Configuration reference
This page lists every configuration key the framework recognizes, with its default value and lifecycle. For how configuration is loaded and merged — file formats, the multi-file loader, list operators, module config — see Concepts → Configuration.
How to read this page
Precedence. Values are resolved lowest-to-highest:
struct defaults < config file(s) < NUCLEUS_* env vars
Environment variables. Every key maps to a NUCLEUS_-prefixed
variable. Flat keys use one underscore (port →
NUCLEUS_PORT); nested keys join segments with a double
underscore (databases.<alias>.url →
NUCLEUS_DATABASES__<ALIAS>__URL).
Lifecycle.
| Tag | Meaning |
|---|---|
stable | Key name and semantics are contract surfaces on the v1.x line. |
transitional | Supported, but semantics may still refine before freezing. |
experimental | No compatibility guarantee yet. |
removed | No longer accepted; the notes name the replacement. |
To see the value every key actually resolves to in a running deployment —
and which file or variable set it — use
nucleus config print --effective
(CLI overview).
Server
| Key | Default | Lifecycle | Notes |
|---|---|---|---|
host | 0.0.0.0 | stable | Bind host. |
port | 8080 | stable | Bind port. |
read_timeout | 30s | stable | HTTP read timeout. |
write_timeout | 60s | stable | HTTP write timeout. |
idle_timeout | 120s | stable | HTTP idle timeout. |
request_timeout | 30s | transitional | Per-request handler deadline: past it the client receives a 503 and the handler's context is cancelled. 0 keeps the default; a negative value (-1s) disables the timeout on every route. Streaming routes are better exempted with timeout_exempt_paths. Before this key existed the deadline silently reused read_timeout. |
timeout_exempt_paths[] | [] | transitional | URL path prefixes served without the request timeout and with the raw response writer (Flush/Hijack work): server-sent events, long polls, large downloads. Requests whose Accept header asks for text/event-stream are exempt without listing them. |
tls_cert_file | "" | transitional | PEM certificate (chain) file. When both tls_cert_file and tls_key_file are set, App.Run serves HTTPS directly (ListenAndServeTLS); when either is empty the server speaks plain HTTP (terminate TLS at a reverse proxy instead). |
tls_key_file | "" | transitional | PEM private-key file paired with tls_cert_file. |
Database
| Key | Default | Lifecycle | Notes |
|---|---|---|---|
database_default | default | stable | Primary DB alias used by app.DB. |
databases.<alias>.url | databases.default.url=sqlite://nucleus.db | stable + experimental | Stable schemes: sqlite://, postgres://, postgresql://, mysql://; exploratory schemes: sqlserver:///mssql://, oracle://. |
databases.<alias>.max_open | 25 | stable | Per-alias pool max open conns (inherits primary if omitted). |
databases.<alias>.max_idle | 5 | stable | Per-alias pool max idle conns (inherits primary if omitted). |
databases.<alias>.max_lifetime | 5m | stable | Per-alias conn max lifetime (inherits primary if omitted). |
MultiSite and MultiTenant
| Key | Default | Lifecycle | Notes |
|---|---|---|---|
multisite.enabled | false | stable | Enable host-based site resolution. |
multisite.default_site | default | stable | Fallback site when host does not match configured patterns. |
multisite.sites.<site>.hosts[] | [] | stable | Exact host or wildcard (*.example.com) patterns per site. |
multisite.sites.<site>.database | database_default | stable | Default DB alias for the site. |
multisite.sites.<site>.tenant_database_alias_template | "" | stable | Optional per-site tenant DB alias template (tenant_%s or {tenant}). |
multitenant.enabled | false | stable | Enable tenant resolution. |
multitenant.resolver | subdomain | stable | subdomain or header. |
multitenant.header | X-Tenant-ID | stable | Header used when resolver is header. |
multitenant.default_tenant | "" | stable | Optional fallback tenant id. |
multitenant.require_isolated_db | true | stable | Security-by-default guard: rejects shared DB alias routing across tenants. |
multitenant.require_tenant_storage | false | stable | Storage operations with no tenant in context fail (storage.ErrNoTenantInContext) instead of silently using the shared unprefixed key space. Off: the first degradation logs one WARN. The storage probe of /healthz and /readyz checks the backing store below tenant scoping, so it neither warns nor fails under either setting. |
multitenant.database_alias_template | tenant_%s | stable | Global tenant DB alias template (%s or {tenant}). |
multitenant.tenants.<tenant>.site | "" | stable | Optional site binding for a tenant mapping. |
multitenant.tenants.<tenant>.database | "" | stable | Explicit tenant DB alias mapping. |
Redis and Sessions
| Key | Default | Lifecycle | Notes |
|---|---|---|---|
redis_url | "" | stable | Optional Redis endpoint for queue/session features. |
session_lifetime | 72h | stable | Server-side session lifetime. |
session_store | memory | stable | Supported values: memory, sql, redis. |
session_redis_url | "" | stable | Redis override for session backend. |
session_table | nucleus_sessions | stable | SQL session table name. |
session_cookie_name | session | stable | Session cookie name. |
session_cookie_domain | "" | stable | Session cookie domain. |
session_cookie_path | / | stable | Session cookie path. |
session_cookie_secure | true | stable | Session cookie Secure attribute. Secure-by-default — the cookie refuses to ride over plain HTTP. Local development over http:// must opt out with session_cookie_secure: false. Mirrors the CSRF cookie posture. |
session_cookie_samesite | lax | stable | SameSite policy string. |
session_idle_timeout | 0 | stable | Optional idle timeout override. |
session_redis_prefix | nucleus:sessions: | stable | Session Redis key prefix. |
session_memcached_servers[] | [] | transitional | host:port list for session_store: memcached. |
Cache (cache.*)
| Key | Default | Lifecycle | Notes |
|---|---|---|---|
cache.provider | memory | experimental | memory (each instance its own), sql (the default database, in the table nucleus createcachetable creates), redis (registered by importing github.com/jcsvwinston/nucleus/pkg/cache/rediscache; without the import the application refuses to start and names nucleus add redis-cache), or a name a backend registered with cache.RegisterProvider. An unknown name is refused at startup, never replaced by the memory cache. |
cache.redis_url | "" | experimental | The server of the redis backend; empty uses redis_url. cache.provider: redis with neither set is refused at load. The server must answer at startup. |
cache.prefix | nucleus:cache: | experimental | The namespace of every key the redis backend writes, so the cache can share a server with the session store and the job queue. Quote it in YAML (prefix: "shop:cache:"): a value ending in : is otherwise read as the start of a mapping. |
cache.table | nucleus_cache_entries | experimental | The sql backend's table. |
Auth
| Key | Default | Lifecycle | Notes |
|---|---|---|---|
jwt_secret | "" | stable | Single-secret HS256 used as legacy fallback when jwt_keys is empty. Must be at least 32 bytes when set — a shorter secret is a boot error (enforced since v1.2.0; generate one with openssl rand -base64 32). Tokens carry no kid header. When jwt_keys[] is non-empty this key is ignored. See jwt_keys[] for the production multi-key path. |
jwt_expiry | 24h | stable | JWT lifetime default. |
jwt_issuer | "" | stable | Issuer claim (iss) stamped into every token minted by App.JWT. Used by both single-secret and multi-key managers. |
jwt_audience | "" | transitional | Audience stamped into every token the app mints and required of every token it accepts; empty leaves aud unchecked. The issuer is always verified. |
jwt_keys[] | [] | stable | Ordered keyset consumed by App.New to build a *auth.JWTManager via auth.NewJWTManagerFromKeys. Each entry is a JWTKeySpec sub-object — see table below. When non-empty, jwt_secret is ignored. |
jwt_current_kid | "" | stable | kid value that identifies the active signing key within jwt_keys[]. Must match one entry's kid. New tokens are signed with this key; all keyset keys remain valid for validation. |
jwt_revocation | false | stable | Turns on the token denylist: Validate refuses a token whose jti has been revoked through JWTManager.Revoke. Off by default because a bearer token is cheap precisely when validating it touches nothing shared — this trades that for one lookup per request. The list is kept in the session store, so `session_store: sql\ |
jwt_keys[] entry fields (JWTKeySpec)
Exactly one of secret_env / pem_path / pem_env must be set per entry — key material is never read from tracked config files:
| Field | Type | Notes |
|---|---|---|
kid | string | Unique key identifier stamped in token kid header. Required. |
algorithm | string | HS256, RS256, or ES256. Required. |
secret_env | string | Resolver reference to the HMAC secret (HS256 only). See reference forms below. |
pem_path | string | Filesystem path to a PEM-encoded private key (RS256: RSA, PKCS#1/PKCS#8; ES256: ECDSA P-256, SEC1/PKCS#8). Rejects PEM with trailing content. |
pem_env | string | Resolver reference to PEM bytes (RS256 / ES256). See reference forms below. |
Reference forms accepted by secret_env and pem_env (plain names read the environment; the aws-sm: scheme reads AWS Secrets Manager via the standard credential chain):
| Reference | Resolved from |
|---|---|
MY_VAR | environment variable MY_VAR (historical behaviour) |
env:MY_VAR | environment variable MY_VAR (explicit form) |
aws-sm:<secret-id> | AWS Secrets Manager secret <secret-id> |
aws-sm:<secret-id>#<json-key> | one string field of a JSON-object AWS secret |
RBAC
| Key | Default | Lifecycle | Notes |
|---|---|---|---|
rbac_policy_file | "" | stable | Path to Casbin RBAC CSV policy file. Feeds the core authz enforcer (pkg/authz.Enforcer). CSV rows require a 4th column (allow / deny) — the model uses deny-override semantics. Programmatic callers use Enforcer.AddPolicy (auto-stamps allow) and Enforcer.Deny. Auto-discovered at rbac_policy.csv, config/rbac_policy.csv, or rbac/rbac_policy.csv when the key is empty. Loaded by the default stack, and by an application built WithoutDefaults() only with WithAuthz(); without it the key is ignored with an ERROR line at boot. |
admin_rbac_policy_file | "" | removed | Removed in v0.12.0. Use rbac_policy_file |
Admin (removed — moved to the orbit module)
| Key | Former default | Lifecycle | Migration |
|---|---|---|---|
admin_prefix | /admin | removed | Use modules.orbit.prefix (see orbit module docs). |
admin_title | Nucleus Admin | removed | Use modules.orbit.title. |
admin_auth_database | "" | removed | Use modules.orbit.auth_database. |
admin_bootstrap_username | "" | removed | Use modules.orbit.bootstrap_username. |
admin_bootstrap_email | "" | removed | Use modules.orbit.bootstrap_email. |
admin_bootstrap_password | "" | removed | Use modules.orbit.bootstrap_password. |
admin_live_exclude_patterns[] | [/admin] | removed | Use modules.orbit.live_exclude_patterns. |
admin_cluster_enabled | false | removed | Use modules.orbit.cluster_enabled. |
admin_cluster_redis_url | "" | removed | Use modules.orbit.cluster_redis_url. |
admin_cluster_channel | nucleus:admin:live:v1 | removed | Use modules.orbit.cluster_channel. |
admin_cluster_node_id | "" | removed | Use modules.orbit.cluster_node_id. |
admin_cluster_token | "" | removed | Use modules.orbit.cluster_token. |
admin_trace_url_template | "" | removed | Use modules.orbit.trace_url_template. |
Mail
| Key | Default | Lifecycle | Notes |
|---|---|---|---|
mail_driver | noop | stable | Built-in and plugin-backed provider selection: noop (delivers nothing), memory (keeps every message, for tests), log (writes every message to the application log; refused outside env: development), smtp, or <name> for a nucleus-plugin-<name> on PATH. |
mail_from | noreply@localhost | stable | Default sender. |
smtp_host | "" | stable | SMTP host. |
smtp_port | 587 | stable | SMTP port. |
smtp_user | "" | stable | SMTP user. |
smtp_pass | "" | stable | SMTP password. |
mail_circuit_breaker.enabled | true | stable | Wrap mail.Sender.Send with a pkg/circuit breaker. noop driver is never wrapped. Healthy (SMTP HELO probe) bypasses the breaker so /healthz observes recovery. |
mail_circuit_breaker.failure_threshold | 5 | stable | Consecutive Send failures required to trip the breaker open. |
mail_circuit_breaker.cooldown | 30s | stable | Time the breaker stays open before admitting half-open probes. |
mail_circuit_breaker.half_open_max_concurrent | 1 | stable | In-flight probe budget while half-open. |
Plugins (plugins.*)
| Key | Default | Lifecycle | Notes |
|---|---|---|---|
plugins.allow_external | true | stable | false refuses every external plugin and command, whatever the lists say. Settable from the environment as NUCLEUS_PLUGINS__ALLOW_EXTERNAL. |
plugins.allowed | [] | stable | When it has entries, the only capability plugins that run: a provider runs a capability only when an entry names both. Each entry is {provider, capabilities}; an entry with no provider, no capabilities, or a capability that is not domain.action is refused at load. Empty restricts nothing until v2.0.0. |
plugins.allowed.<n>.provider | — | stable | The <provider> of nucleus-plugin-<provider> (the mail_driver value for a mail plugin). |
plugins.allowed.<n>.capabilities | — | stable | The capabilities the provider may run, e.g. [mail.send]. |
plugins.commands | [] | stable | When it has entries, the only external commands nucleus <name> dispatches to nucleus-<name>. Empty restricts nothing until v2.0.0. |
Module Jobs and Webhooks
| Key | Default | Lifecycle | Notes |
|---|---|---|---|
jobs_provider | memory | stable | pkg/tasks provider that executes module jobs: memory (in-process scheduler + workers; pending jobs are lost on restart), asynq (Redis-backed, durable; requires jobs_redis_url) or sql (durable on the application's own database; no broker, and cron runs too — the leader is elected with a lease row, so exactly one replica fires each entry). Added in v1.4.0; sql added later. |
jobs_redis_url | "" | stable | Redis connection URL for the asynq jobs provider (e.g. redis://localhost:6379/0). Required when jobs_provider: asynq — validated at boot; ignored by memory. Added in v1.4.0. |
jobs_concurrency | 4 | stable | Number of concurrent job workers. 0 uses the provider default. Added in v1.4.0. |
jobs_retention | 24h | stable | How long the sql provider keeps a FINISHED job before deleting it. 0 uses the default; a negative value keeps them for ever. Dead jobs are never removed by retention — purge-archived is the deliberate act that removes those. Ignored by the other providers. |
jobs_table | nucleus_jobs | stable | Table the sql jobs provider keeps its queue in. Created if missing, per dialect. Ignored by the other providers. |
jobs_queues | [] | stable | Order the sql provider serves queues in: the first is served before the second, which is how priority is expressed. Empty means the single queue default. Ignored by the other providers — asynq takes its weights from its own configuration. |
jobs_scheduler_lock | true | stable | Leader election for the asynq scheduler over a Redis lock (SET NX + TTL): with multiple replicas exactly one process ticks the cron entries; workers run on every replica either way. false opts out and the boot log WARNs that each replica fires every job. Ignored by the memory provider. |
webhooks_prefix | /webhooks | stable | URL prefix under which module webhook routes mount: <prefix>/<module-name><path>. With csrf_enabled: true the framework exempts this prefix from CSRF automatically — webhooks authenticate by HMAC signature (X-Nucleus-Signature), not by CSRF token. Added in v1.4.0. |
Transactional Outbox (outbox.*)
| Key | Default | Lifecycle | Notes |
|---|---|---|---|
outbox.enabled | false | transitional | Enables the outbox: the table lives on the default database and the leasing dispatcher starts with the app. |
outbox.table_name | nucleus_outbox | transitional | Name of the outbox table. |
outbox.lease_duration | 30s | transitional | How long a claimed message stays leased to one dispatcher instance before another may claim it. |
outbox.max_retries | 5 | transitional | Delivery attempts before a message is marked failed. |
outbox.retry_backoff | 1s | transitional | Base delay for the exponential retry backoff. |
outbox.lease_owner | `` (per-instance) | transitional | Identifies this instance in lease rows. Empty derives nucleus-<hostname>-<pid>; set explicitly for a stable identity (e.g. a k8s pod name). Before v1.8.2 every process shared the literal nucleus-app. |
outbox.missing_route_policy | error | transitional | What a dispatcher does with a leased message whose topic has no registered bridge: error fails it; ignore releases it for the instance that can deliver it (heterogeneous fleets). Invalid values fail startup. |
outbox.bridges.<n>.name | — | transitional | Bridge instance name (required; also the routing target name). Bridge entries are configured in files only: the NUCLEUS_* double-underscore mapping has no list-index syntax, so per-entry env overrides do not apply. |
outbox.bridges.<n>.type | — | transitional | Bridge type. webhook posts each message over HTTP; plugin hands it to an external capability plugin (nucleus-plugin-<provider>, queue.publish or webhook.deliver); kafka is disabled and fails boot. Any other type logs a WARN and the bridge is dropped. plugin added in v1.32.0. |
outbox.bridges.<n>.config.url | — | transitional | Webhook bridge, and plugin bridge with capability: webhook.deliver: delivery endpoint URL (required). |
outbox.bridges.<n>.config.pattern | * | transitional | Webhook and plugin bridges: topic pattern routed to this bridge (e.g. orders.*). |
outbox.bridges.<n>.config.headers | — | transitional | Webhook bridge, and plugin bridge with webhook.deliver: extra HTTP headers sent on every delivery (e.g. an Authorization value). The contract headers X-Outbox-Payload-Encoding and X-Nucleus-Signature cannot be overridden. Plugin bridge with queue.publish: the headers of every published message. |
outbox.bridges.<n>.config.secret | "" | transitional | Webhook bridge, and plugin bridge with webhook.deliver: HMAC-SHA256 signing secret. When set, every delivery carries X-Nucleus-Signature: sha256=<hex> over the exact body — the same scheme module webhooks verify, so consumers share one verifier. Empty: deliveries are unsigned and the boot log WARNs once per bridge. Added after v1.4.0. |
outbox.bridges.<n>.config.payload_encoding | base64 | transitional | Webhook bridge, and plugin bridge with webhook.deliver: wire shape of the body's payload field. base64 (default) is the classic shape every release up to v1.4.0 emits (the payload as a base64 JSON string); json opts in to embedding the payload's JSON document verbatim. Every delivery declares its actual shape in X-Outbox-Payload-Encoding, whatever the mode. Added after v1.4.0. |
outbox.bridges.<n>.config.provider | — | transitional | Plugin bridge: the <provider> of the nucleus-plugin-<provider> executable on PATH (required). The plugins block decides whether it may run; a provider it refuses, a missing executable, or one that does not advertise the capability stops startup. Added in v1.32.0. |
outbox.bridges.<n>.config.capability | — | transitional | Plugin bridge: queue.publish (each message published under its topic, keyed by its id) or webhook.deliver (the plugin is handed the request the webhook bridge would send, signed the same way). Required. A plugin failure it marks non-retriable (exit 10 or 30) sends the message to the dead letter at once; every other failure is retried with the outbox's backoff. Added in v1.32.0. |
outbox.bridges.<n>.config.timeout | 10s | transitional | Plugin bridge: how long one delivery may take; the plugin is killed when it runs out and the delivery is retried. Added in v1.32.0. |
outbox.bridges.<n>.config.topic | — | transitional | Plugin bridge with queue.publish: the queue topic every message is published under. Empty publishes each message under its own outbox topic. Added in v1.32.0. |
outbox.bridges.<n>.config.method | POST | transitional | Plugin bridge with webhook.deliver: the HTTP method of every delivery. Added in v1.32.0. |
Observability and Security
| Key | Default | Lifecycle | Notes |
|---|---|---|---|
log_level | info | stable | Logger level selector. |
log_format | json | stable | json/text formatter contract. |
log_redact_extra_keys[] | [] | transitional | Additional log attribute keys whose values the structured logger redacts, on top of the built-in denylist (observe.DefaultRedactedKeys). Case-insensitive. Use it for app-specific sensitive fields (ssn, card_number, …). There is intentionally no config key to disable redaction — redaction is on by default and turning it off requires an explicit code-level opt-out via observe.NewLoggerWithRedaction |
otlp_endpoint | "" | stable | Optional OTLP-HTTP push endpoint for traces + metrics. Requires the exporters/otlp module (nucleus add otlp): the core keeps the OpenTelemetry SDK and links no exporter, so a non-empty endpoint without the module stops startup with the import to add. Coexists with metrics_path — when both are set, the MeterProvider feeds both readers. |
metrics_path | /metrics | stable | Mount path for the Prometheus / OpenMetrics scrape endpoint. Empty string disables the endpoint. Requires the exporters/prometheus module (nucleus add prometheus); the core links no exporter. When the module is imported and the path is non-empty, App.New attaches a Prometheus reader to the OTel MeterProvider and serves it at this path with application/openmetrics-text content type. Without the module: a path you wrote yourself stops startup with the import to add, while the untouched default only logs at startup that metrics are not being served and the application boots with the path answering 404. The endpoint carries no authentication of its own: when enabled, restrict access at the network / reverse-proxy layer (allow-list your scraper) or mount your own guard middleware in front of it. |
metrics_public | true | stable | Whether the metrics endpoint is seeded into the anonymous bootstrap allow-list. true (default, the historical behaviour) lets Prometheus scrape without credentials — pair it with network-layer restrictions. false keeps /metrics OUT of the allow-list, so the default-deny RBAC enforcer gates it like any user route: grant your scraper an explicit policy (e.g. p, metrics-scraper, /metrics, *) plus JWT auth, or use a reverse-proxy guard. On an application built WithoutDefaults(), false gates the endpoint only with WithAuthz(); without it the key is ignored with an ERROR line at boot while the endpoint is served. Added in v1.3.0. |
sql_driver_instrumentation | false | stable | Opt-in driver-level SQL instrumentation. false (default): the observability live SQL feed shows only model.CRUD traffic and the database/sql driver is not wrapped — zero hot-path cost. true: the driver is wrapped so direct db.QueryContext/ExecContext statements that bypass CRUD (outbox dispatch, SQL session stores, migrations, raw SQL) also reach the feed. CRUD statements are not double-recorded (de-duplicated by a context marker). Adds a small per-direct-statement cost when enabled; the expensive sanitize+emit still runs only when a subscriber is attached. Added in v1.3.0. |
profiling_enabled | false | transitional | Mounts the Go profiler under /debug/pprof (cmdline, profile, symbol, trace, plus the named profiles: heap, goroutine, allocs, block, mutex, threadcreate). Off by default: net/http/pprof registers itself on http.DefaultServeMux at import time, and heap and goroutine dumps carry live data, so a framework that mounted it unguarded would put that data on every application's public surface. It is deliberately not in the anonymous bootstrap allow-list — unlike /healthz, /livez and /readyz, on the default stack these routes answer only to whoever the application's own authorization policy names. Turn it on when an on-call engineer needs a profile of a process that is misbehaving; the application logs a warning at startup while it is on. An application built WithoutDefaults() without WithAuthz() has no policy to put these routes behind: they answer anyone who reaches the port, and the warning is an error naming the profiler (in production, naming heap dumps of the process); from v2.0.0 that combination refuses to start unless WithAuthz() guards the profiler. With WithAuthz() the routes sit behind default-deny, as on the default stack. |
rate_limit_requests | 0 | stable | Sustained rate budget (0 disables). |
rate_limit_window | 1m | stable | Rate limit refill window. |
rate_limit_burst | 0 | stable | Burst capacity over sustained budget. |
rate_limit_by_route | false | stable | Per-route token bucket partitioning. |
rate_limit_by_role | false | stable | Per-role token bucket partitioning. |
cors_origins[] | [] (empty) | stable | CORS allow-list. Empty (the default) DENIES cross-origin requests — no CORS headers are emitted. A non-empty list restricts CORS to exactly these origins; the historical allow-all is the explicit opt-in ["*"]. |
cors_allow_credentials | false | stable | Emit Access-Control-Allow-Credentials: true. Only honored when cors_origins is non-empty — the Fetch standard forbids credentials with the * wildcard. |
csrf_enabled | false | stable | Mounts the router's CSRF middleware (router.WithCSRF): Sec-Fetch-Site origin verification with a double-submit token fallback. Opt-in because CSRF protection only applies to cookie/session-authenticated browser routes — a pure Bearer-token API does not need it. The mvc scaffold ships with true. Added in v1.3.0. |
csrf_exempt_paths[] | [] | stable | URL path prefixes excluded from CSRF validation (Bearer-only subtrees such as /api/, signature-authenticated webhook receivers). Only meaningful with csrf_enabled: true. |
csrf_insecure_cookie | false | stable | Development-only opt-out: disables the Secure attribute on the CSRF cookies so the double-submit flow works for plain-HTTP non-browser clients (Go cookiejar over http://127.0.0.1). Mirrors session_cookie_secure: false. Never enable in production. |
trusted_proxies[] | [] (empty) | stable | Upstream proxy addresses (IPs or CIDRs) whose X-Forwarded-For / X-Real-IP headers the RealIP middleware honors. Empty (the default) IGNORES forwarding headers and uses the immediate peer (r.RemoteAddr) as the client IP, preventing header-spoofed rate-limit evasion and audit-log poisoning. Set to your load balancer / reverse-proxy ranges (e.g. ["10.0.0.0/8"]) when Nucleus runs behind one. |
auth_backends[] | [] (empty) | stable | ORDERED list of authentication backend names (registered with auth.RegisterBackend; the application's own user table appears here under the name given to WithUserProvider, local by default). Order is meaningful: [ldap, local] consults the directory first and falls through to the local table when the directory cannot be reached — a backend that is UNAVAILABLE is skipped, one that REJECTS ends the attempt. A name that is not registered fails at boot; a repeated name is rejected because the list is a chain, not a set. Empty means no chain is built. |
storage.<provider>.* | — | stable | Configuration subtree of a REGISTERED third-party storage provider (storage.RegisterProvider). The framework validates only that the section belongs to a registered name — a misspelled section is still an unknown key; the provider validates its own contents through storage.Config.BindProvider, where a key its struct does not declare is an error. Built-in providers use their typed sections (storage.s3.*, storage.local.*, …) instead. |
auth.<backend>.* | — | stable | Configuration subtree of a REGISTERED authentication backend (auth.RegisterBackend), keyed by the name used in auth_backends. Same rule as storage.<provider>.*: the framework validates only that the section belongs to a registered name — a misspelled section is still an unknown key — and the backend validates its own contents through auth.BackendConfig.Bind, where a key its struct does not declare is an error. A backend that needs no settings needs no section. A section whose backend is NOT listed in auth_backends is an ERROR: the chain is its only consumer, so it would boot clean and be read by nobody. Added in v1.15.0. |
auth_federated[] | [] (empty) | stable | Browser-redirect identity providers (OIDC, SAML). Each entry declares name (the INSTANCE: its URL segment, its auth.<name>.* subtree and its sign-in button), provider (the registered protocol type) and an optional display_name. Instance and type are separate fields because two identity providers of the same protocol — a corporate tenant and a partner one — is an ordinary configuration. Independent of auth_backends: a federated flow has no credentials to hand to a chain. A provider nobody registered fails at boot, naming what is registered; two entries sharing a name fail, because they would share a callback URL. Added in v1.17.0. |
public_base_url | "" (empty) | stable | The address the BROWSER reaches this application at, without a trailing slash. REQUIRED when auth_federated is non-empty: the callback URL an operator registers with their identity provider is derived from it, and behind a reverse proxy or in a container it is not the address the process binds. The resolved callback URLs are logged at startup. Added in v1.17.0. |
http_interceptors[] | [] (empty) | stable | ORDERED list of registered request interceptors (interceptor.Register), outermost first: the first name sees the request first and the response last. Order is the behaviour, not a detail — authentication before rate limiting and rate limiting before authentication are different systems. A name that is not registered fails at boot naming what is, a factory that errors fails boot, and a repeated name is rejected. Interceptors mount INSIDE the framework's own middleware, after the request ID, the session and the observability hook. Added in v1.17.0. |
interceptors.<name>.* | — | stable | Configuration subtree of a REGISTERED request interceptor, keyed by the name used in http_interceptors — the same pairing as auth_backends with auth.<backend>.*: the list orders, the subtree configures. The framework validates only that the section belongs to a registered name; the interceptor validates its own contents through interceptor.Config.Bind, where a key its struct does not declare is an error. Added in v1.17.0. |
Localization, Static, Storage, Environment
| Key | Default | Lifecycle | Notes |
|---|---|---|---|
default_locale | en | stable | Default i18n locale. Read by the i18n CLI pair and by the runtime: when compiled catalogs are loaded, a request whose Accept-Language matches no catalog locale falls back to this locale (see pkg/i18n). |
locales_path | locales/ | stable | Locale catalog path. makemessages/compilemessages write catalogs here; at startup the app loads any compiled JSON bundles under it and, when at least one locale exists, mounts the Accept-Language negotiation middleware. |
static_prefix | /static/ | stable | Static route prefix. |
templates_dir | internal/web/templates | stable | Root of the HTML template tree. Loaded recursively at startup (since v1.8.2): every .html registers under its path relative to this dir with forward slashes (fieldservice/index.html); root files keep their flat name (base.html); {{define}} blocks register under their declared names. The startup log reports templates loaded with the count; a present-but-empty dir logs a WARN. |
static_root | static/ | stable | Static collection target root. |
storage_driver | — | removed | Removed in v0.12.0. Use storage.provider |
storage_path | — | removed | Removed in v0.12.0. Use storage.local.path |
env | development | stable | Environment mode (development/production). |
debug | false | stable | Debug feature toggles. |
profile | `` (none) | stable | Named preset applied over the loaded config. dev swaps every backing-service selection for its no-dependency counterpart — SQLite database (extra aliases dropped; an already-SQLite URL is kept), in-memory sessions and jobs, local filesystem storage, no-op mailer — so the same file boots with zero external services. Unknown values fail config load. |
Unified Storage (storage.*)
| Key | Default | Lifecycle | Notes |
|---|---|---|---|
storage.provider | local | stable | Backend: local (built in), or s3, gcs, azure — each a module of its own (`providers/storage-s3 |
storage.default | private | stable | Default object visibility (private/public). |
storage.public_url_base | "" | stable | Base URL for public objects (CDN or provider). |
storage.public_paths | {} | stable | Maps URL paths to storage key prefixes. |
storage.s3.endpoint | "" | stable | Custom S3 endpoint (MinIO, R2). Empty = AWS. |
storage.s3.bucket | "" | stable | Primary S3 bucket name. |
storage.s3.region | "" | stable | AWS region. |
storage.s3.access_key_id | "" | stable | AWS access key. Accepts a plain string (literal value) or the credential-source shape: value / env_var / file / secret_manager sub-keys. |
storage.s3.secret_access_key | "" | stable | AWS secret key. Plain string or credential-source shape (value/env_var/file/secret_manager). |
storage.s3.session_token | "" | stable | AWS session token for temporary credentials. Plain string or credential-source shape. |
storage.s3.use_path_style | false | stable | Path-style URLs (required for MinIO). |
storage.s3.public_bucket | "" | stable | Dedicated public bucket name. |
storage.s3.create_bucket_if_missing | false | stable | Provision the bucket(s) at startup when missing. Opt-in; without it a missing bucket fails app.New loudly. Was advertised by the startup error but rejected by the loader until v1.8.1. |
storage.gcs.bucket | "" | stable | Primary GCS bucket. |
storage.gcs.public_bucket | "" | stable | Dedicated public GCS bucket. |
storage.gcs.credentials | "" | stable | GCS service-account credentials. Plain string or credential-source shape (typically file: for the mounted SA JSON); empty = Application Default Credentials. |
storage.azure.account_name | "" | stable | Azure storage account name. Plain string or credential-source shape. |
storage.azure.account_key | "" | stable | Azure storage account key. Plain string or credential-source shape. |
storage.azure.container | "" | stable | Primary container name. |
storage.azure.public_container | "" | stable | Public container name. |
storage.local.path | storage/ | stable | Local filesystem root (dev only). |
storage.cleanup.enabled | false | stable | Enable automatic temp file cleanup. |
storage.cleanup.interval | 1h | stable | Cleanup run frequency. |
storage.cleanup.prefix | _tmp/ | stable | Prefix for temporary objects. |
storage.cleanup.max_age | 24h | stable | Max age before temp files are purged. |
storage.circuit_breaker.enabled | true | stable | Wrap remote provider ops (Put/Get/Delete/Exists/List/Copy/SignedURL) with a pkg/circuit breaker. Local provider is never wrapped. PublicURL is pass-through. ErrNotFound is not counted as a failure. |
storage.circuit_breaker.failure_threshold | 5 | stable | Consecutive op failures required to trip the breaker open. |
storage.circuit_breaker.cooldown | 30s | stable | Time the breaker stays open before admitting half-open probes. |
storage.circuit_breaker.half_open_max_concurrent | 1 | stable | In-flight probe budget while half-open. |
Module configuration (modules.*)
The modules.<name>.* namespace is reserved for mounted modules.
Each module owns its own schema, declared as struct tags on its typed config —
the framework does not validate those keys against the tables above. Two
practical limits: the NUCLEUS_MODULES__* env-var pattern is not
applied (module config comes from files or code), and
nucleus config print --effective excludes modules.*
values (module schemas are open-ended and may carry secrets). See
Concepts → Configuration → Module-specific configuration
for the full authoring guide.