Skip to content

The v1 docs follow nauthera-server's main branch and its 0.x releases on the way to 1.0, so fields and APIs can still change.

v1

Configuring nauthera-server

Docs version v1 (main)Nauthera is pre-1.0, in a design-partner programUpdated

The server loads its configuration in this order of precedence: defaults < YAML file < environment < secret files. Point it at a file with --config (or NAUTHERA_SERVER_CONFIG). Environment variables win over the YAML file, and a secret file named by a _FILE variable wins over both. See Secrets from files.

nauthera-server --config /etc/nauthera/config.yaml

The annotated reference is config.example.yaml in the nauthera-server repository.

Environment variables

Every key has the prefix NAUTHERA_SERVER_. To get the variable name, upper-case the dotted key and replace . and - with _:

server.http.read_header_timeout  ->  NAUTHERA_SERVER_SERVER_HTTP_READ_HEADER_TIMEOUT
database.dsn                     ->  NAUTHERA_SERVER_DATABASE_DSN
tracing.sample_ratio             ->  NAUTHERA_SERVER_TRACING_SAMPLE_RATIO

Durations are Go duration strings ("15s", "30m"). Lists are comma-separated.

Prefer the file. The file is decoded strictly, so a mistyped key is a startup error that names it. A mistyped environment variable is silently ignored, and you run on a default you believe you overrode. The Helm chart renders config to a file for this reason.

Secrets from files

Keys that hold secrets also accept a mounted file. Append _FILE to the variable name:

NAUTHERA_SERVER_DATABASE_DSN_FILE=/var/run/secrets/db/uri
NAUTHERA_SERVER_DATABASE_PASSWORD_FILE=/var/run/secrets/db/password
NAUTHERA_SERVER_CACHE_PASSWORD_FILE=/var/run/secrets/redis/password
NAUTHERA_SERVER_CACHE_SENTINEL_PASSWORD_FILE=/var/run/secrets/redis/sentinel-password
NAUTHERA_SERVER_MFA_ENCRYPTION_KEY_FILE=/var/run/secrets/mfa/key
NAUTHERA_SERVER_KEYS_ENCRYPTION_KEY_FILE=/var/run/secrets/keys/kek
NAUTHERA_SERVER_MAIL_SMTP_PASSWORD_FILE=/var/run/secrets/smtp/password
NAUTHERA_SERVER_AUTH_CAPTCHA_SECRET_FILE=/var/run/secrets/captcha/secret

A file's contents, with surrounding whitespace trimmed, outrank both the YAML file and the plain environment variable. An empty file therefore blanks a value set in either place. A file that cannot be read stops the server at startup.

The retired-key lists (mfa.retired_keys, keys.retired_keys) have no _FILE form, so pass them through the environment. audit.checkpoint_key_path is a path, not a secret value, so it has no _FILE form either.

Top-level sections

SectionWhat it configures
serverThe three listeners: server.http (:8080, timeouts, body cap, TLS), server.admin (:9090, pprof), server.grpc (:9091, mTLS with tls.client_ca_file and tls.allowed_clients, both certificate and CA read at each handshake, allow_insecure and reflection), plus shutdown_timeout and drain_delay.
logginglevel (debug, info, warn or error), format (json or console), development.
databasePostgreSQL, the only database: driver (postgres, the default and only accepted value), a dsn or discrete host, port, user, password, name, sslmode (verify-full by default, which checks the database's certificate and name; any libpq mode is accepted) and sslrootcert (the CA file a database with a private CA needs), pgbouncer, pool sizing, the session bounds statement_timeout (30s), lock_timeout (10s) and idle_in_transaction_timeout (1m), migrate, and runtime_role, the role the server serves requests as when it does not own the schema. See Backups & Upgrades.
cacheA Redis-compatible cache: enabled, mode (standalone at address, or sentinel and cluster at addresses, with master_name for Sentinel), username, password, sentinel_password, replica_acks and replica_ack_timeout (replicas that must confirm each write), db, tls and tls_ca_file (the CA the Redis certificate is verified against, read only with tls), timeouts and pool size. Required for any issuer that is not loopback. The server refuses to start against a Redis whose maxmemory-policy may evict keys.
metricsenabled, path (default /metrics on the admin listener).
rate_limitPer-IP limits: enabled, rps, burst, backend (memory, a token bucket per replica, or redis, a fixed window shared by every replica) and window. While Redis cannot answer, the redis backend limits each replica on its own rps and burst bucket, and nauthera_rate_limit_degraded reads 1. See Rate Limits & Lockout.
securitytrusted_proxies, trusted_proxies_warn_prefix, client_ip.mode and client_ip.header, network_zones, hsts, hsts_max_age, frame_deny, content_security_policy, sign_in_opener_policy (same-origin by default; unsafe-none for a relying party that signs in through a popup and reads window.opener), cors.*. See The client address and Network zones.
issuerurl and the token lifetimes: access_token_ttl (10m), id_token_ttl (10m), refresh_token_ttl (720h), auth_code_ttl (60s).
keysSigning keys: provider (memory for development, or file), signing_key_path, rsa_signing_key_path, verification_key_paths and rsa_verification_key_paths for extra public keys to publish during a rotation, default_alg, plus the per-organization key-encryption key encryption_key and its retired_keys. See Keys & Secrets.
tracingOpenTelemetry over OTLP/gRPC: enabled, endpoint, insecure, sample_ratio, environment.
authbrute_force.* (lockout, per-IP and per-identifier throttles, and max_concurrent_verifies, the process-wide limit on password hashing), session.* (cookie, idle and absolute TTL, history_retention), pending.*, session_management.enabled, registration.enabled, password.* (a new password's min_length, 8, and max_length, 128, at most 1024; refuse_common; and breach_lookup.*, an opt-in Pwned Passwords range check that sends only the first five characters of the password's SHA-1: enabled, url, timeout, fail_open), captcha.* (below), password_reset.*, email_verification.* and invitation.* (link validity and max_requests), and admin.* (subjects, bootstrap_secret_path, max_auth_age). See Sessions, Consent & Logout.
mfaThe deployment's second-factor policy (required, methods, passwordless, remember_browser_for, spare_zones, rules; below), the one-time-password seed cipher (provider, memory or file, encryption_key, retired_keys), issuer (the label shown in authenticator apps), drift, max_failures, lock_duration, recovery_code_count, and passkey.* (below). See Multi-Factor Authentication.
orgsdefault_slug, the main organization's slug (default), and main.*, settings applied to the main organization at every startup (below).
auditenabled, level, include and exclude globs, fail_closed, buffering, refusal coalescing, retention (9600h), partition_ahead_months, partition_maintenance (internal, or external when nauthera-server maintain runs as its own job), signed chain checkpoints (checkpoint_interval, checkpoint_key_path, and checkpoint_key_required, which refuses to start without a key), and export.* for forwarding the stored log. See Audit Log.
fapirequire_pushed_authorization_requests, which requires PAR deployment-wide.
fipsrequired, which puts the main organization under the FIPS policy and refuses to start unless the Go FIPS 140-3 module is on and pinned to a frozen version. A binary built with GOFIPS140=v1.0.0 passes with no GODEBUG setting; an ordinary build run with GODEBUG=fips140=on is refused. See Hardening.
mailThe deployment's outbound relay: smtp.host, smtp.port (587), smtp.tls (starttls, implicit, or none for a relay without TLS), smtp.allow_plaintext_auth, smtp.username, smtp.password, from.address, from.name, reply_to, local_name, timeout; and organizations, per-organization relays by slug, whose credentials are read from username_file and password_file at every delivery.
geoipdatabase, an optional GeoLite2-City or GeoIP2-City file that lets a new-sign-in alert name where the sign-in came from.

The client address

Per-IP throttles, the rate limiter and the audit log key on the client address. security.client_ip.mode says where it comes from:

ModeAddress
socketThe connecting peer.
x-forwarded-forX-Forwarded-For from a peer in trusted_proxies, walked right to left.
headerThe one header client_ip.header names (X-Real-IP, True-Client-IP or CF-Connecting-IP), from a peer in trusted_proxies.

Unset, the mode is x-forwarded-for when trusted_proxies is set and socket otherwise; any mode but socket needs trusted_proxies. Every workload inside a trusted range can name its own client address, so list your proxies' own addresses. A range covering every address (0.0.0.0/0, ::/0) is refused at startup, and one wider than trusted_proxies_warn_prefix (/16 for IPv4, /48 for IPv6) is reported with a warning. Behind a proxy, socket makes every client share one bucket; the dashboard's overview then tells platform administrators which proxies it saw and what to set.

Network zones

security.network_zones names networks a sign-in policy can refer to: a lowercase name (letters, digits, hyphens) mapped to IPv4 and IPv6 ranges in CIDR notation. It is a map, so it can be set only in the file:

security:
  network_zones:
    office: ["203.0.113.0/24", "2001:db8:1::/48"]
    vpn: ["198.51.100.0/24"]

A sign-in is in a zone when its client address, as client_ip reads it, falls in one of the ranges. Every sign-in's audit record, completed or refused, names its zones (network_zones). An organization declares its own zones beside these and may not reuse a name. A range that covers every address of its family, or a name that is not a lowercase label, refuses startup.

The second-factor policy

The first six mfa keys are the deployment's second-factor policy. It is a floor: each organization, and then each client, may tighten it and never loosen it. An organization or client sets its own through the admin API, the control plane, or its Organization or OIDCClient resource.

KeyDefaultMeaning
mfa.requiredfalseA user with no permitted factor is sent through enrolment before a session is issued, at sign-in and at self-service registration.
mfa.methods[totp, passkey]The second-factor methods offered at all: totp, passkey and email (an emailed code). Empty means totp and passkey; email is offered only when listed.
mfa.passwordlessfalseOpens sign-in with a passkey alone, for every organization and client that also opens it. Refused at startup when mfa.methods lists methods and passkey is not one.
mfa.remember_browser_for0sA user who holds a second factor is not asked for it again on a browser that passed one for them within this window, while their password and factors are unchanged. 0 remembers no browser; at most 4320h. Organizations and clients may shorten it or always challenge.
mfa.spare_zones[]The security.network_zones in which a user who holds a second factor is not asked for it. Only these deployment zones can relax the challenge. An undeclared name refuses startup.
mfa.rules[]Rules that narrow the policy for the sign-ins that meet them (file only).

A sign-in spared its second factor, by a remembered browser or a zone, is single-factor: its session and ID token carry the first factor's amr and no mfa, and its audit record says mfa_spared: remembered_browser or mfa_spared: network_zone. An identity provider with spareSecondFactor spares the sign-ins through it outside this policy (mfa_spared: identity_provider; see IdentityProvider). An organization's admin console always asks for the second factor, because its step-up needs a sign-in that used it.

Each rule has the fields name, outside_zones, inside_zones, new_network, new_browser, methods and require. It holds when every condition it sets does: the client address is in none of outside_zones or in one of inside_zones, the network is one the user has never completed a sign-in from, or the browser has not passed a second factor for the user. It then narrows the methods to methods, requires a second factor (require, which no exemption spares), or both. A rule cannot loosen anything.

mfa:
  spare_zones: [office]
  rules:
    - name: passkey-off-site
      outside_zones: [office, vpn]
      methods: [passkey]
      require: true

The audit record of a completed or interrupted sign-in names the rules that held (mfa_rules). A rule with no name, a repeated name, no condition, no effect, an unknown method or an undeclared zone refuses startup.

Passkeys

KeyDefaultMeaning
mfa.passkey.rp_id""The relying-party ID passkeys are scoped to: the issuer's host, or a registrable domain it is under. Empty serves no passkey ceremony. A public suffix, a domain the issuer is not under, or an IP-address issuer is refused at startup.
mfa.passkey.rp_nameNautheraThe name an authenticator shows when it creates a credential.
mfa.passkey.origins[]The origins (scheme://host[:port]) a ceremony may run on, each under rp_id. Empty means the issuer's origin alone.
mfa.passkey.user_verificationpreferredrequired, preferred or discouraged. Under required, a ceremony whose authenticator did not verify the user is refused.
mfa.passkey.timeout2mHow long a ceremony and its challenge last, 30s to 10m.
mfa.passkey.offer_interval720hWhere passwordless sign-in is open, a user who signs in with a password and holds no passkey is offered one. Declining is remembered this long, at least 24h.

See Passkeys.

Captcha

Unless auth.captcha.provider is none, registration, password-reset requests and verification-link resends need a captcha solution, and so does a sign-in once the address typed, or the caller's IP, has failed sign_in_after times recently. A missing, wrong, expired or reused solution is refused with 400 captcha_failed.

KeyDefaultMeaning
auth.captcha.provideraltchaaltcha is the built-in proof-of-work; turnstile (Cloudflare Turnstile), hcaptcha and recaptcha (Google reCAPTCHA v2) are hosted providers; none asks for no captcha.
auth.captcha.altcha.max_number1000The largest counter a challenge hides, from 1 to 100000. Solving costs the browser max_number / 2 PBKDF2 derivations on average, and checking costs the server one; the default takes a desktop browser well under a second.
auth.captcha.altcha.validity5mHow long a challenge can be solved and sent back, from 1m to 1h.
auth.captcha.site_key""A hosted provider's public site key. Required by a hosted provider.
auth.captcha.secret""A hosted provider's secret key, also from _FILE. Required by a hosted provider. In the Helm chart, secrets.captchaSecret.
auth.captcha.sign_in_after3Recent failed sign-ins, by the address typed or from the caller's IP, after which sign-in asks for the captcha too. 0 never asks at sign-in.

altcha calls no third party, so it works air-gapped, and uses only FIPS-approved primitives. Its challenges are signed with a key derived from mfa.encryption_key, so every replica accepts the others', and each is spent once in the shared cache. A hosted provider must be reachable from the server (through HTTPS_PROXY when set). While it cannot be reached, every request that needs the captcha is refused with 503 unavailable, so pick altcha where availability matters more than the provider's bot scoring.

Invitations

An administrator can create a user with invite, which creates the account without a password and emails a single-use link to /accept-invitation, where the user chooses a password. The organization needs an email relay. auth.invitation.validity (168h, from 1m to 30 days) is how long a link works, and auth.invitation.max_requests (5) how many links one account is sent within one validity window, by an administrator or on the user's own request; an administrator's resend beyond it is refused with 429. A user who waits on an invitation and signs in, signs up, or asks for a reset or verification link is emailed a new invitation instead (see Emailed links). See Admin API & Users.

The main organization

The main organization (slug default) is created by a migration. Each orgs.main setting given is applied to it at every startup, before it is served, and recorded as an organization.updated by the system. The admin API refuses to change a setting given here (409, naming it); one left out stays editable. The control plane never changes the main organization. In the Helm chart these are config.orgs.main.*.

KeyMeaning
orgs.main.display_nameThe name the hosted UI shows, 1 to 200 characters.
orgs.main.fapi_modeHolds the main organization's clients to the FAPI 2.0 Security Profile. It is read only at startup, so this key is the way to change it. Without an ES256 key the server refuses to start in FAPI mode.
orgs.main.email_verification_requiredKeeps an account with an unverified address out of a session until it follows an emailed link.
orgs.main.brandingThe main organization's default branding document, as text (YAML or JSON), because the loader lowercases a nested object's keys. Every sign-in page under it wears it, with a client's own branding laid over it field by field. An unsafe document stops startup; an empty one clears it.

None of them has a default: left out, the setting is whatever the admin API last set.

External identity providers for social sign-in have no configuration keys. Each organization declares its own through an IdentityProvider resource or nauthera-cli create identity-provider, and the admin API and the dashboard list them.

Production guards

Every issuer must be an https URL with a host and no path, query or fragment. There is no exception for local development: the session cookie's Secure attribute follows the issuer's scheme, so a plaintext issuer would run in a configuration production never uses. A plaintext listener behind a TLS-terminating proxy is fine, because the issuer names what clients reach.

When the issuer host is not loopback, the server also refuses to start unless:

  • cache.enabled is true (otherwise authorization codes, pushed requests and other single-use state would live in one replica's memory), and
  • keys.provider is file and mfa.provider is file (otherwise keys would be regenerated on every restart).

localhost and names under .localhost count as loopback, so local development gets a real HTTPS issuer without having to run Redis.

On any issuer, loopback included, mfa.provider: memory is also refused when mfa.required is true.

Three mistakes are not caught at startup:

  • A --config path that does not exist is ignored, and the server runs on defaults and environment variables.
  • Without keys.encryption_key, each replica only logs a warning and makes up its own key in memory. An organization created meanwhile works only on the replica that created it, until that replica restarts. See Keys & Secrets.
  • An empty secret file is not refused. It blanks its key, and for keys.encryption_key that means the in-memory key above. See Secrets from files.

Example: minimal production file

issuer:
  url: https://auth.example.com
database:
  driver: postgres
  host: postgres.example.svc
  user: nauthera
  name: nauthera
  sslmode: verify-full
  migrate: true
cache:
  enabled: true
  address: redis.example.svc:6379
  tls: true
keys:
  provider: file
  signing_key_path: /etc/nauthera/keys/ec/tls.key
  rsa_signing_key_path: /etc/nauthera/keys/rsa/tls.key
mfa:
  provider: file
security:
  trusted_proxies: ["10.244.1.7/32"]
  client_ip:
    mode: x-forwarded-for
  hsts: true
server:
  grpc:
    tls:
      enabled: true
      cert_file: /etc/nauthera/grpc/tls.crt
      key_file: /etc/nauthera/grpc/tls.key
      client_ca_file: /etc/nauthera/grpc/ca.crt
      allowed_clients:
        - identity: "dns:nauthera-operator"
          operations: [clients, brandings, identity_providers, ldap_directories]
          organizations: [default]
          namespaces: ["*"]
audit:
  checkpoint_key_path: /etc/nauthera/audit/checkpoint.pem
  checkpoint_key_required: true

Supply the database password, the MFA encryption key and the key-encryption key through the _FILE variables above. Each allowed_clients entry must name the organizations and namespaces it may act in, or the server refuses to start.