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.
Bootstrap YAML
Section titled “Bootstrap YAML”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 |
Per-subsystem REST endpoints
Section titled “Per-subsystem REST endpoints”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.
kv_settings keys
Section titled “kv_settings keys”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.
Post-quantum TLS
Section titled “Post-quantum TLS”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:
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.
Secrets encryption at rest
Section titled “Secrets encryption at rest”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:
# read posturecurl 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 controlcurl -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.
Hardcoded constants
Section titled “Hardcoded constants”A few values are compile-time constants in internal/daemon/constants.go — they’re not runtime-tunable:
- DNS port
53, DoT port853 - 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.
Related
Section titled “Related”- Configuration — the bootstrap YAML schema in detail.
- gwctl CLI — wrapper around the same REST API.
- REST API — every mutation endpoint.
- Operations playbook — what to check / rotate at what cadence.