docs: rewrite for people running nxdns; query-history refusals name their remedy and a refused start gives no wrong hints

This commit is contained in:
2026-10-21 17:03:00 +02:00
parent fbc8fd7882
commit c21e5a9542
31 changed files with 1186 additions and 3877 deletions
+37 -133
View File
@@ -1,90 +1,55 @@
# Back up and restore
The configuration is the only state worth keeping. `nxdns export` writes it out as a ZON file and `nxdns import` writes one back. The query log is deliberately not part of a backup: it is expendable history, and if it is missing it gets recreated empty.
The configuration is the only state worth backing up. The query history is not part of a backup, and the blocklists download again on their own.
**Which authority the service runs under decides what the backup *is*.** Check the start log:
What the backup is depends on the mode nxdns runs in. The start log says which:
- `authority: database` — `config.db` holds the configuration. Back it up with `nxdns export`, and restore with `nxdns import` or by replacing the database file.
- `authority: file (<path>)` — that file holds the configuration, and it is already a text file you can keep in git. **The file is the backup.** Restoring means putting the file back and restarting; the database rebuilds itself from it. `config.db` is a cache of the file in this mode, not the thing to preserve.
- `authority: database`: the configuration lives in `config.db`. Back it up with `nxdns export` and restore it with `nxdns import`. The rest of this page is about this mode.
- `authority: file (<path>)`: the configuration is that file, and the file is the backup. To restore, put the file back and start nxdns; it rebuilds its database from the file.
The rest of this page covers database mode unless it says otherwise.
The commands below use the scratch lab from [enable DoH and DoT](enable-doh-and-dot.md), data directory `/tmp/nxdns-lab/data`. On a real install drop `--data-dir` and the default `/var/lib/nxdns` applies.
The commands below use the default data directory, `/var/lib/nxdns`. Add `--data-dir DIR` if yours is elsewhere.
## Back up
One command, against a stopped or a running server:
```sh
nxdns export --data-dir /tmp/nxdns-lab/data --out /tmp/nxdns-lab/backup.zon
nxdns export --out /var/backups/nxdns-config.zon
```
```
wrote /tmp/nxdns-lab/backup.zon
wrote /var/backups/nxdns-config.zon
```
The write is a temp file plus a rename, so an interrupted export leaves no half-written backup, and the result is mode 0600:
This works while nxdns is running. The file is written in one step, so an interrupted export leaves no half-written backup, and it is created at mode 0600 because it carries the admin password's hash:
```sh
stat -c '%a %n' /tmp/nxdns-lab/backup.zon
```
```
600 /tmp/nxdns-lab/backup.zon
```
That mode is not decoration. The file carries `web.password_hash`:
```sh
grep password /tmp/nxdns-lab/backup.zon
```
```
```zon
.password = null,
.password_hash = "$argon2id$v=19$m=19456,t=2,p=1$hOjjnTrTZ6kU8XrwQuJ7ZFC3B5LumaB4hRe7kBbzJ6Q$YsC2rCTDKv96eEwPhs+D6vbDliLogEZph8EkagKSuy8",
.password_hash = "$argon2id$v=19$m=19456,t=2,p=1$…",
```
Treat backups as secrets. `.password` is always exported as `null` — the plaintext is never stored anywhere — so the file re-imports without anyone knowing the password. `null` and `""` are different statements here: `null` means the file says nothing about the password, while an empty `password_hash` would disable authentication. See [Password and hash](../reference/configuration.md#password-and-hash).
The plaintext password is never stored, so `.password` is always `null`, and the file restores the same password without anyone knowing it. Keep backups as private as the database.
Without `--out` the export goes to stdout, where the file mode is your redirect's problem:
Without `--out`, the export goes to standard output.
An export holds configuration only: settings, groups, upstreams, devices you named, blocklist sources, rules and local records. It leaves out what the server records while it runs, such as when a device was last seen or how many domains a list held, so two exports of the same configuration are identical.
## Restore onto a new machine
On an empty data directory, `import` loads the backup:
```sh
nxdns export --data-dir /tmp/nxdns-lab/data | head -10
```
```
// nxdns configuration
// generated by `nxdns export` from the running configuration
.{
.upstream = .{
.attempt_timeout_ms = 2500,
.read_timeout_ms = 3000,
.total_timeout_ms = 5000,
},
.dns = .{
.bind_ipv4 = "127.0.0.1",
```
Runtime facts are left out on purpose: client first-seen and last-seen times, rule creation times, per-source domain counts and checksums. They are things a running server produces, not configuration, and including them would make two exports taken minutes apart differ.
## Restore onto a fresh data directory
This is the normal restore: new machine, new disk, empty data directory. Nothing to overwrite, so nothing to authorise.
```sh
nxdns import /tmp/nxdns-lab/backup.zon --data-dir /tmp/nxdns-lab/data-restored
nxdns import /var/backups/nxdns-config.zon
```
```
info(migrations): config.db migrated from schema version 0 to 1
imported /tmp/nxdns-lab/backup.zon
imported /var/backups/nxdns-config.zon
```
The migration line is expected: `import` creates and migrates the database before writing to it.
Under systemd, run it as root and then `chown -R nxdns:nxdns /var/lib/nxdns` before you start the service. Under Docker, see [Database mode instead](install-with-docker.md#database-mode-instead).
## Restore over an existing database
## Restore over an existing configuration
**Stop the server first.** `import` rewrites configuration underneath a process that read it at startup, and a running server picks up only part of it — filtering follows the new rows at the next reload, while upstreams, listeners and settings stay at their boot values until a restart.
Stop nxdns first. A running server read its configuration when it started, and would only pick up part of the change.
```sh
systemctl stop nxdns
@@ -92,97 +57,36 @@ nxdns import /var/backups/nxdns-config.zon
systemctl start nxdns
```
`import` converges the database onto the file. Rows the file still names are matched and updated in place; rows it no longer names are deleted. That last part is what a restore of an old backup does to everything added since, so it takes a flag:
```sh
nxdns import /tmp/nxdns-lab/old-backup.zon --data-dir /tmp/nxdns-lab/data
```
`import` makes the database match the file. Entries the file names are added or updated. Entries it does not name would be deleted, which is what restoring an old backup does to everything added since, so `import` refuses until you confirm, and changes nothing:
```
FAIL import: this file would delete rows the database holds (upstreams 1); re-run with --allow-delete to apply it
import failed: DestructiveImport
```
That exits 2 and rolls the transaction back, so nothing is half-applied. The message names every table that would lose rows, which is usually enough to tell an intended restore from the wrong file. Say `--allow-delete` when deleting is what you mean:
The message names what would go. If that is what you want, run it again with `--allow-delete`:
```sh
nxdns import /tmp/nxdns-lab/old-backup.zon --allow-delete --data-dir /tmp/nxdns-lab/data
nxdns import /var/backups/nxdns-config.zon --allow-delete
```
```
imported /tmp/nxdns-lab/old-backup.zon
```
Renaming a group or changing an upstream's URL counts as a deletion too: the old entry goes and a new one arrives.
A restore that only puts back what is already there needs no flag at all, and neither does one that only adds rows. The flag is about deletion specifically — including deletion in disguise: renaming a group, or correcting a typo in an upstream URL, changes the row's identity, so the engine sees one row gone and one arrived.
A restore keeps what does not belong in a backup. A blocklist whose URL is still in the file keeps its download, so nothing is fetched again. Devices nxdns discovered on the network keep their history, and a device the backup names keeps the first-seen and last-seen times the database already had. A device you named by hand that the backup does not name is deleted with the rest.
What survives a restore is worth knowing before you take an old backup out of the drawer. Blocklist download state is kept for every source whose URL the file still names — checksum, counters, compiled files, and the row id they are keyed by — so restoring does not cost a re-download. Devices the server discovered from traffic are kept whole, and one the backup names keeps the first-seen and last-seen the database already held, so a restore never restamps your network as newly arrived. What does go is a client the backup does not name and that was named by hand: that row is declarative, and it is deleted with the rest.
## Check a backup
> The three `systemctl` lines are the only commands on this page that were not
> run: this machine has no installed nxdns unit (`systemctl status nxdns`
> answers `Unit nxdns.service could not be found.`) and `systemctl stop`/`start`
> need root. The `nxdns import` between them is the same command the lab blocks
> above run, which were executed here as written.
## Restoring the database file itself
Copying `config.db` back into place works too, and it is the fastest restore on a machine that still has one. Two rules, and the second one is where restores go wrong:
1. **Take the copy from a stopped instance**, or use `nxdns export` instead. A copy taken while nxdns is running catches the main file without the changes sitting in its write-ahead log.
2. **Delete any stale `config.db-wal` and `config.db-shm` beside the file you restore.** SQLite silently discards a write-ahead log that does not match the database it sits next to. It does not warn, and it does not fail — it just answers from the main file, so a restore quietly loses its own tail.
Exporting the same configuration twice gives the same bytes, so a backup that loads and exports back unchanged is complete. To check one without touching the real data directory, load it into a scratch directory and compare:
```sh
systemctl stop nxdns
rm -f /var/lib/nxdns/config.db-wal /var/lib/nxdns/config.db-shm
cp /var/backups/config.db /var/lib/nxdns/config.db
chown nxdns:nxdns /var/lib/nxdns/config.db
chmod 0600 /var/lib/nxdns/config.db
systemctl start nxdns
nxdns import /var/backups/nxdns-config.zon --data-dir /tmp/nxdns-check
nxdns export --data-dir /tmp/nxdns-check --out /tmp/nxdns-check.zon
diff /var/backups/nxdns-config.zon /tmp/nxdns-check.zon && echo "backup is complete"
```
> Not verified on this host: these need root, an installed unit and
> `/var/lib/nxdns`, none of which exist here.
## What a backup leaves out
None of this applies in file mode. There `config.db` is derived state — restore the configuration file and start the service, and the first reconcile rebuilds the database from it.
- `querylog.db`, the query history. nxdns creates an empty one when it is missing. To keep the history as well, copy `querylog.db` while nxdns is stopped.
- `blocklists/`, the downloaded lists. They come back from the sources the configuration names.
## Verify a backup
The round trip is byte-stable: exporting, importing and exporting again gives an identical file. That is the cheapest check that a backup is complete and that it will load.
```sh
nxdns export --data-dir /tmp/nxdns-lab/data --out /tmp/nxdns-lab/backup2.zon
diff /tmp/nxdns-lab/backup.zon /tmp/nxdns-lab/backup2.zon \
&& echo "round trip is byte-identical"
```
```
wrote /tmp/nxdns-lab/backup2.zon
round trip is byte-identical
```
The same check works across data directories — export from the restored copy and diff against the backup you restored from:
```sh
nxdns export --data-dir /tmp/nxdns-lab/data-restored --out /tmp/nxdns-lab/backup3.zon
diff /tmp/nxdns-lab/backup.zon /tmp/nxdns-lab/backup3.zon \
&& echo "restored database exports identically"
```
```
wrote /tmp/nxdns-lab/backup3.zon
restored database exports identically
```
## What is not in the backup
Everything under the data directory other than `config.db`:
- `querylog.db` — expendable history, recreated empty when absent.
- `blocklists/` — compiled snapshots. They are rebuilt from the sources named in the configuration, so restoring the configuration is enough; the first refresh after a restore downloads them again.
The data directory layout is in [files and directories](../reference/files-and-directories.md).
## A backup before every upgrade
There is no downgrade path. Schema migrations run forward automatically at startup and before `export` and `import`; nothing walks them back, and `nxdns check` does not run them at all. Take an export before installing a new binary — see [upgrade](upgrade.md).
Every `nxdns` command on this page was executed on this host as written. The `systemctl`, `cp`, `chown` and `chmod` lines were not: they need root and an installed unit, and both blocks holding them say so.
[Files and directories](../reference/files-and-directories.md) lists everything in the data directory.