Skip to content

Settings reference

The Gateway’s “settings” don’t live in one place. They’re spread across three storage mechanisms, each with different semantics:

Mechanism What lives there Mutation path
Bootstrap YAML Network mode, interfaces, listener addresses, initial tunnels, mesh / DNS / TLS / auth defaults Edit config.yaml + restart daemon
Per-subsystem REST API Day-to-day mutable state: routing policies, devices, privacy classes, DNS rules, alert rules, backup schedule POST/PUT/DELETE against /api/v1/<kind> (or use the UI / gwctl)
kv_settings SQLite table A small set of cross-subsystem strings: AI endpoint URL, node ID, secrets passphrase Internal — managed by the daemon, not directly edited

The Settings UI page (and GET /api/v1/settings) shows a unified view of the bootstrap config, so you rarely need to think about which storage mechanism backs a particular field.

See Configuration for the complete schema. The most-touched keys:

Key Default Notes
listen.api_addr 127.0.0.1:8080 Where the REST API + UI bind
log.level info debug / info / warn / error
network.mode primary primary / embedded / portable
network.wan_iface eth0 Internet uplink
network.lan_iface eth1 LAN-facing interface
dns.listen_addr 0.0.0.0:53 Resolver bind
dns.upstream_servers (none) Bare IPs of upstream resolvers
dns.upstream_mode plain plain / doh / dot for upstream queries
dns.backend integrated integrated or pihole
dns.filtering_enabled true Whether the daemon applies its own filter chain
auth.enabled false When false, all requests are admin
auth.local_net 127.0.0.0/8 CIDR string. Clients from here are admin for GET/HEAD/OPTIONS; state-changing verbs still need a credential
reconcile.interval_sec 60 Drift-scan cadence. Validated >= 5
tls.enabled false Whether to terminate TLS in the daemon

Everything you’d change from the UI day-to-day goes through dedicated endpoints. See the REST API reference. The most common ones:

Subsystem Mutation endpoint(s)
Routing policies /api/v1/routing/policies (CRUD + /toggle, /apply)
Route profiles /api/v1/profiles
Exceptions /api/v1/exceptions
Privacy classes /api/v1/privacy-classes
Privacy toggles /api/v1/privacy/{block-doh,block-ipv6,block-webrtc,force-dns,dns-mode}
DNS filter rules /api/v1/dns/filter-rules
DNS zones / records /api/v1/dns/zones, /api/v1/dns/records
Tunnels /api/v1/tunnels (CRUD + /up, /down)
Bridges /api/v1/bridges
Alert rules /api/v1/alerts/rules
Backup config /api/v1/backup/config
Federation roles /api/v1/federation-roles

These are not “settings” in the kv-pair sense — each is a structured object with its own schema. The UI represents them as tables / forms; the API accepts JSON bodies.

The actual kv_settings SQLite table is small. It holds string values keyed by single names (not dotted paths). The keys currently in use:

Key What it stores
ai_default_model Ollama model name to use for the AI assistant validator.
ai_ollama_url URL of the Ollama (or compatible) endpoint.
node_id Stable UUID for this daemon instance (used in federation and audit).
secrets_passphrase Internal — used to derive keys for secret-at-rest encryption.
pq_crypto_mode Post-quantum TLS posture: default / preferred / required.

These are managed by the daemon and exposed through the Settings UI page where relevant; you don’t normally edit them by hand.

pq_crypto_mode controls how aggressively the daemon insists on post-quantum key exchange for its outbound TLS (DoH upstreams, OCSP, webhook receivers, mesh control plane):

Value Behaviour
default Use Go’s built-in CurvePreferences (X25519MLKEM768-preferred in Go 1.24+).
preferred Pin MLKEM-first explicitly, classic fallback. Locks the order so a future Go release dropping PQ can’t downgrade.
required Strict PQ only. Classic peers fail handshake. Use only for known PQ-capable mesh (Go 1.23+, OpenSSL 3.5+, recent BoringSSL, Firefox 132+, Chrome 124+).

Update via:

Terminal window
curl -X PUT https://gateway.lan:8080/api/v1/pq-crypto \
-H "Authorization: Bearer $API_KEY" \
-d '{"mode":"preferred"}'

Read current state (mode + active curve list) via GET /api/v1/pq-crypto. New TLS sessions pick up the change immediately; existing sessions keep their negotiated curve until they close.

The secrets_passphrase key drives AES-256-GCM encryption of the secrets SQLite table — API keys, mesh bearer tokens, proxy credentials. Each stored value is wrapped as enc:base64(salt[16] || nonce[12] || ciphertext+tag). The encryption key is PBKDF2-HMAC-SHA256(passphrase, salt) with 100 000 iterations and a fresh 16-byte salt per row.

Auto-init. On first boot the daemon generates a random 32-byte hex passphrase and stores it in two places: kv_settings.secrets_passphrase and the fallback file /root/.gateway-secrets-passphrase (mode 0400). Either one is enough to start the daemon; the boot routine reconciles them at every start so a wiped kv or deleted file is automatically restored from the other source.

Status and rotation:

Terminal window
# read posture
curl https://gateway.lan:8080/api/v1/secrets/encryption-status \
-H "Authorization: Bearer $API_KEY"
# → {"encrypted": true, "total_secrets": 12, "encrypted_count": 12}
# rotate to a passphrase you control
curl -X POST https://gateway.lan:8080/api/v1/secrets/set-passphrase \
-H "Authorization: Bearer $API_KEY" \
-d '{"passphrase":"REDACTED"}'
# disable encryption (writes plaintext back)
curl -X POST https://gateway.lan:8080/api/v1/secrets/clear-passphrase \
-H "Authorization: Bearer $API_KEY"

Rotation safety. The handler decrypts every secret with the OLD passphrase before writing anything. If any decrypt fails, the rotation aborts with HTTP 500 and the old passphrase is preserved. The re-encrypt step then writes all rows under the new passphrase in a single transaction so a mid-batch failure rolls back cleanly — you never end up with a SQLite table where some rows are old-encrypted and some are new-encrypted.

Loss-recovery posture. If kv_settings.secrets_passphrase is wiped and the fallback file is deleted and you haven’t set your own passphrase, the daemon auto-generates a new one on next boot and existing enc:-prefixed rows become permanently undecipherable. If you want recoverability beyond root-on-this-box, set your own passphrase and store a copy outside the daemon’s host.

A few values are compile-time constants in internal/daemon/constants.go — they’re not runtime-tunable:

  • DNS port 53, DoT port 853
  • WireGuard MTU defaults
  • Reconciler validation floor (5 s)
  • Internal timeouts

If you need a different value, the path is a daemon recompile + redeploy, not a setting toggle.