Gates / frontend (push) Successful in 1m2s
Gates / test (push) Successful in 1m38s
Gates / package (push) Successful in 5m5s
Gates / test-aarch64 (push) Successful in 6m30s
Gates / container (push) Successful in 15s
CI / gates (push) Successful in 13m30s
189 lines
8.9 KiB
Markdown
189 lines
8.9 KiB
Markdown
# 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.
|
|
|
|
**Which authority the service runs under decides what the backup *is*.** Check the start log:
|
|
|
|
- `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.
|
|
|
|
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.
|
|
|
|
## 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
|
|
```
|
|
|
|
```
|
|
wrote /tmp/nxdns-lab/backup.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:
|
|
|
|
```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
|
|
```
|
|
|
|
```
|
|
.password = null,
|
|
.password_hash = "$argon2id$v=19$m=19456,t=2,p=1$hOjjnTrTZ6kU8XrwQuJ7ZFC3B5LumaB4hRe7kBbzJ6Q$YsC2rCTDKv96eEwPhs+D6vbDliLogEZph8EkagKSuy8",
|
|
```
|
|
|
|
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).
|
|
|
|
Without `--out` the export goes to stdout, where the file mode is your redirect's problem:
|
|
|
|
```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
|
|
```
|
|
|
|
```
|
|
info(migrations): config.db migrated from schema version 0 to 1
|
|
imported /tmp/nxdns-lab/backup.zon
|
|
```
|
|
|
|
The migration line is expected: `import` creates and migrates the database before writing to it.
|
|
|
|
## Restore over an existing database
|
|
|
|
**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.
|
|
|
|
```sh
|
|
systemctl stop nxdns
|
|
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
|
|
```
|
|
|
|
```
|
|
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:
|
|
|
|
```sh
|
|
nxdns import /tmp/nxdns-lab/old-backup.zon --allow-delete --data-dir /tmp/nxdns-lab/data
|
|
```
|
|
|
|
```
|
|
imported /tmp/nxdns-lab/old-backup.zon
|
|
```
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
> 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.
|
|
|
|
```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
|
|
```
|
|
|
|
> Not verified on this host: these need root, an installed unit and
|
|
> `/var/lib/nxdns`, none of which exist here.
|
|
|
|
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.
|
|
|
|
## 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.
|