This guide is the production runbook for an installed Sable node. It covers routine health checks, logs, metrics, configuration changes, updates, backups, and the first response to common failures. Configuration field semantics remain in the configuration guide.
sable install creates a hardened systemd service with these paths:
| Path | Purpose |
|---|---|
/usr/local/bin/sable |
Root-owned command and default service executable |
/var/lib/sable/bin/sable |
Opt-in service executable when installed with --enable-web-updates |
/etc/sable/sable.toml |
Node configuration |
/etc/sable/tls |
Initial or imported node certificate material |
/var/lib/sable |
Database, caches, block lists, ACME state, vault key, and cluster identity |
/etc/systemd/system/sable.service |
Service unit |
The service runs as the dedicated sable user with only
CAP_NET_BIND_SERVICE. ProtectSystem=strict makes the system executable
directory read-only. By default an administrator therefore uses
sudo sable update. An installation created with --enable-web-updates runs
the service copy under /var/lib/sable, which is already confined to the
unprivileged service account and writable inside the sandbox.
Run these checks after an install, update, certificate change, cluster handoff, or host restart:
systemctl status sable --no-pager
curl --fail --silent http://127.0.0.1:5380/api/v1/health
sable version
sable config check --config /etc/sable/sable.toml
sable query --server 127.0.0.1:53 example.com AThe loopback HTTP listener is the recovery and process-health endpoint in the native installation. The public console and DoH endpoint normally share HTTPS port 443. Probe every transport the deployment actually publishes, using a certificate name trusted by the probing host:
sable query --transport tcp --server 127.0.0.1:53 example.com A
sable query --transport tcp-tls --server dns.example.net:853 example.com A
sable query --transport quic --server dns.example.net:853 example.com A
sable query --transport doh \
--server https://dns.example.net/dns-query example.com AFor a cluster, also confirm that every node is Online and In sync, with matching applied/current generations. Query each member through its node preset before declaring a rollout or handoff complete.
sudo systemctl restart sable
sudo systemctl stop sable
sudo systemctl start sableConsole workflows for updates, restores, and cluster identity changes can
request a controlled restart. The process exits with its restart-request code;
the installed unit's Restart=on-failure policy starts it again. A process run
directly from a terminal exits instead, so restart it under the intended
supervisor.
Sable handles SIGTERM gracefully. It stops accepting new work, drains bounded
workers within server.shutdown_timeout, and persists eligible cache state
before exiting. Do not use SIGKILL for routine restarts.
The system journal is the first place to look when startup, the database, or the persistent log writer itself is failing:
journalctl -u sable -n 200 --no-pager
journalctl -u sable --since '30 minutes ago'
journalctl -u sable -fThe console's Logs → Server view starts with the current process ring buffer.
Use Settings → Logging or [server_log] to control persistence, minimum
level, and retention. Entries still go to stderr and the journal even when
database persistence is unavailable.
Logs → Queries uses the query-log database. It supports time, client, domain, type, response-code, source, and transport filters; cursor navigation; and export. Expanding a query shows the persisted blocking-policy, cache, resolver, route, and DNSSEC decisions. When the recorder queue is full, events are dropped rather than slowing DNS, and the dropped count appears in metrics.
Query-log retention, server-log enablement/level/retention, and dashboard-history retention hot-reload. Changing query-log or server-log buffer, batch, or flush settings requires a restart because those values define worker lifetimes.
GET /metrics requires an authenticated session or an API token whose selected
role grants metrics.read:
curl --fail --silent \
--header 'Authorization: Bearer sable_pat_REDACTED' \
http://127.0.0.1:5380/metricsWatch at least:
sable_dns_upstream_errors_totalandsable_dns_response_write_failures_totalfor serving failures;sable_dns_response_duration_secondsfor latency by response source, protocol, cache result, and response code;sable_query_log_dropped_totalandsable_query_log_write_errors_totalfor observability loss;sable_block_list_sources_degradedand the per-list failure counters;sable_dnssec_bogus_totaland trust-anchor state;sable_cluster_nodes_connected,sable_cluster_nodes_synchronized, andsable_cluster_node_replication_lag_generationsin a cluster;- cache occupancy and hit/miss counters for workload changes.
Counters describe the local process. Cluster membership gauges report the local node's current view of all members; Sable does not yet aggregate every member's DNS/query counters into one cluster-wide series.
Validate an edited file before it replaces the active configuration:
sable config check --config /etc/sable/sable.tomlWith [config].watch = true, an atomic write or rename triggers a transactional
reload. Sable parses, normalizes, validates, prepares replacement sockets and
runtime state, and activates the candidate before advancing the configuration
revision. A bad candidate leaves the last-known-good runtime serving; inspect
the server log for the rejected field or listener.
Prefer the Settings UI for supported cluster-scoped fields. It uses the same validation and activation path and prevents edits on a replica. Database DSN, administrative listener, security bootstrap, and some node-local changes require a controlled restart rather than hot activation.
Check without changing the executable:
sudo sable update --checkInstall the newest stable release and restart the managed service:
sudo sable updateUse an exact version for a controlled rollout or binary rollback:
sudo sable update --version 1.3.1The updater downloads the platform archive and checksums.txt, verifies the
SHA-256 digest, extracts the expected executable, and runs its version command
before replacing the installed binary. If replacement fails, the old executable
is restored; after a successful replacement it is not retained as a permanent
rollback copy.
Create an application backup before an upgrade. Database and backup-format downgrade compatibility is not yet a published contract, so installing an older binary is not a substitute for restoring a known-good pre-upgrade backup when persistent formats changed.
The console can check for updates in every layout. To let it install them on a native service, opt in while installing or re-run the installer:
sudo sable install --enable-web-updatesThe service remains non-root and cannot write the system PATH; it replaces
only /var/lib/sable/bin/sable, then exits through the controlled-restart path.
This creates a persistence path for code already running as the sable user,
so use it only with console authentication and trusted HTTPS. Run
sudo sable install without the flag to restore the default immutable layout.
Set SABLE_GITHUB_TOKEN when repeated update checks exhaust GitHub's anonymous
API limit. It is used only for GitHub release-metadata requests.
At minimum:
- Export a passphrase-sealed backup before upgrades and material DNS, identity, certificate, or cluster changes.
- Enable scheduled local backups in Settings → Backup, choose a directory
on durable storage, and set the interval and retained archive count. Use an
external
sable backup createtimer when backups must be shipped off-node. - Store archives off-host and separately from the passphrase.
- Run
sable backup inspectin the backup pipeline to reject malformed envelopes and record the source version/time. - Periodically test a restore into an isolated host.
Backups omit query logs and dashboard statistics. If those are operationally important, protect the database separately with backend-appropriate tooling. Never copy a live SQLite file as an application backup; use Sable's archive or a SQLite-aware snapshot.
See backup and restore for passphrase sources, section selection, fresh-host recovery, database overrides, and cluster behavior.
Update a replica first, verify it at the current generation, then update the primary. Use a planned handoff only when the target is online and fully synchronized. Use recovery promotion only after establishing that the former primary is offline.
The clustering guide covers enrollment, replicated/local state, planned handoff, recovery promotion, node removal, rolling updates, and cluster restore behavior.
Containers keep mutable state in /data and run as a non-root user. The default
immutable workflow is to pull the desired image and recreate the container
against the same volume. Pin an exact semantic-version tag for controlled
production rollouts. next tracks release candidates and latest tracks the
newest stable release.
Set SABLE_WEB_UPDATES=true and use a Docker restart policy to opt into console
updates. Sable stores the verified executable under /data/.sable/bin, exits
cleanly when the administrator confirms the restart, and the immutable image
entrypoint launches the staged build. No Docker socket, root user, or additional
capability is required. A newer image version wins over an older staged build;
remove the environment setting to ignore the staged build during recovery or a
pinned rollback.
Publish the administrative listener only on a trusted interface, and configure
Sable HTTPS or a trusted HTTPS reverse proxy before exposing the console beyond
the host. Preserve /data with both volume-level protection and exported Sable
backups.
| Symptom | First response |
|---|---|
| Service will not start | Run sable config check, inspect journalctl -u sable, then check listener conflicts, certificate/key paths, file ownership, and database reachability. |
| Configuration edit has no effect | Confirm file watching is enabled and the configuration revision advanced. A rejected candidate leaves the old runtime active and logs the reason. |
| UDP works but TCP/DoT/DoH/DoQ fails | Check the specific listener, firewall/proxy protocol, certificate names, and whether another process owns its TCP or UDP port. |
| DoH returns an HTTP error | Query the exact /dns-query URL, verify the certificate and HTTP version path, and distinguish it from the administrative API listener. |
| Responses are slow | Inspect the latency histogram by source and cache; compare upstream errors, cache hit rate, and protocol before increasing resource limits. |
| Query logs have gaps | Check sable_query_log_dropped_total, write errors, database health, queue sizing, and retention. DNS intentionally wins over telemetry. |
| Block list is stale | Inspect the Blocking page and per-source health metrics. Sable retains the last successful file and backs off failed downloads. |
| Console says update is blocked | Use sudo sable update or replace the image, or deliberately opt in with sable install --enable-web-updates or SABLE_WEB_UPDATES=true for Docker. |
| Replica refuses a change | Make the cluster-scoped change on the primary. Node-local actions remain available on the replica. |
| Restore is unavailable on a replica | Restore the primary or rebuild/re-enroll the replica; otherwise the next synchronized generation would overwrite it. |
Before manual database repair, cluster-state deletion, or forced rollback:
- stop the affected process if continued writes could alter evidence;
- copy the configuration, data directory, journal excerpt, running version, and relevant exported backup to a protected location;
- record cluster roles and generations from every reachable member;
- avoid editing the SQLite/PostgreSQL schema or content-addressed cluster files by hand.
Report suspected vulnerabilities privately according to the security policy. For reproducible performance investigations, use the benchmark protocol rather than an unrecorded ad hoc load test.