# nxdns nxdns is a DNS sinkhole for your LAN. It blocks the names you do not want, and forwards the rest over an encrypted connection. ```mermaid flowchart LR devices["Your devices"] -- "DNS query" --> nxdns["nxdns"] nxdns -- "answer" --> devices nxdns -- "allowed" --> upstream["Upstream resolvers
DoH / DoT"] upstream -- "answer" --> nxdns nxdns -- "blocked" --> sink["0.0.0.0 / NXDOMAIN"] ``` One static Zig binary. SQLite holds the state and the query log, which the web UI, the REST API and `/metrics` read. ## Features - Blocklist filtering: subscribe to hosts, domain and Adblock Plus lists — whose `@@` exception lines are honoured — plus your own allow and block rules, exact, wildcard (`*.example.com`) or regular expression - Two configuration modes: a database the web UI edits, or a ZON file you keep in git and converge onto at every start - Per-client policy groups: different filtering for the kids' tablet and your workstation - Local DNS records and conditional forwarding for internal zones - Encrypted upstreams: DNS-over-HTTPS and DNS-over-TLS with failover - Built-in DoH and DoT server endpoints, with certificate hot-reload - Bounded in-memory DNS cache with TTL-respecting expiry - Query log with retention limits, live-streamed over SSE - Web UI (embedded in the binary) and a REST API with a served OpenAPI spec - Prometheus-style `/metrics`, per-client rate limiting, disk-full self-protection ## Install **No release exists yet.** This repository has no tags, nothing has been published to , and no container image has been pushed. Every release URL on this page and in the how-to guides is a 404 today, and `docker pull` finds nothing. Until the first tag ships, building from source is the only way to get nxdns. What a tag will publish, once one exists: five assets — two static musl tarballs (`nxdns--x86_64-linux-musl.tar.gz`, `nxdns--aarch64-linux-musl.tar.gz`), `IMAGE-DIGEST.txt` naming the multi-architecture container image by digest, `SHA256SUMS.txt` covering those three files, and `SHA256SUMS.txt.asc`, a detached OpenPGP signature over the checksum file. Verify what you downloaded before you run it: [docs/how-to/verify-a-release.md](docs/how-to/verify-a-release.md), which also says what that signature does and does not prove. ## Quickstart (docker compose) Write a minimal configuration and start the published image. This is what the first release will make possible; it does not work today, because there is no image in the registry to pull: ```sh cd deploy/docker mkdir -p etc-nxdns cat > etc-nxdns/config.zon <<'EOF' .{ .groups = .{ .{ .name = "default" } }, .upstreams = .{ .{ .url = "https://cloudflare-dns.com/dns-query" } }, .web = .{ .password = "choose-a-real-password" }, } EOF NXDNS_VERSION= docker compose up -d ``` The compose file defaults to `:latest`; pin a version for anything you intend to keep running. To run it before a release exists, build the image yourself and name it — `NXDNS_IMAGE=nxdns docker compose up -d` — as [docs/how-to/install-with-docker.md](docs/how-to/install-with-docker.md) describes. DNS is on port 53, the web UI on . The compose file runs `nxdns run --config=/etc/nxdns/config.zon`, which makes that file the configuration: every start reconciles the database onto it, and the UI refuses configuration edits. Edit the file and restart to change anything. Drop the `command:` line to run bare `nxdns run` instead, where the database is the configuration and changes go through the UI, the API, or `nxdns export` / `nxdns import` — the packaged systemd unit does that. Which mode is live is printed at every start (`authority: database` / `authority: file ()`); see [docs/explanation/configuration-model.md](docs/explanation/configuration-model.md). Full install instructions, including the systemd path and the Pi 5 recipe, are in [docs/how-to/install-with-systemd.md](docs/how-to/install-with-systemd.md) and [docs/how-to/install-with-docker.md](docs/how-to/install-with-docker.md). ## Building from source Requires [Zig 0.16.0](https://ziglang.org/download/) and Node.js 24 (for the web UI). C dependencies (SQLite, mbedTLS) are vendored and built by `zig build`. ```sh (cd admin && npm ci && npm run build) # web UI -> admin/dist zig build -Dadmin-dist=admin/dist # native binary -> zig-out/bin/nxdns zig build test --summary all # unit tests ``` The release artifacts come out of the same build graph, so the whole release build runs on a laptop exactly as it runs on the CI runner: ```sh (cd admin && npm ci && npm run build) # required: dist refuses the placeholder VERSION=$(sed -n 's/^[[:space:]]*\.version[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' build.zig.zon) zig build dist -Dversion-string="$VERSION" \ -Dadmin-dist=admin/dist -Doptimize=ReleaseSafe # tarballs -> zig-out/dist/ zig build verify-dist -Dversion-string="$VERSION" \ -Dadmin-dist=admin/dist -Doptimize=ReleaseSafe # the release checks ``` The version comes from `build.zig.zon` because `verify-dist` asserts the two agree; a tag sets both. On the toolchain versions pinned at the top of `.gitea/workflows/gates.yml`, your tarballs hash the same as the published ones of that version. [docs/how-to/verify-a-release.md](docs/how-to/verify-a-release.md) has the rebuild recipe and what to check when a hash differs. ## Documentation Start at [docs/README.md](docs/README.md), which splits the documentation into a tutorial, how-to guides, reference and explanation. - [docs/tutorial/first-run.md](docs/tutorial/first-run.md) — build it, resolve a name, block a domain, on a scratch directory - [docs/how-to/install-with-systemd.md](docs/how-to/install-with-systemd.md) — a real install, including the Raspberry Pi 5 - [docs/how-to/verify-a-release.md](docs/how-to/verify-a-release.md) — checking the hashes and the signature before you install - [docs/reference/configuration.md](docs/reference/configuration.md) — every configuration field - [docs/reference/api.md](docs/reference/api.md) — REST API, auth and SSE - [docs/reference/cli.md](docs/reference/cli.md) — subcommands, flags and exit codes - [docs/explanation/architecture.md](docs/explanation/architecture.md) — module map and design - [PLAN.md](PLAN.md) and [specs/](specs/) — scope, design decisions and per-milestone contracts ## Licence Copyright (c) 2026 Mokhtar Mial. nxdns is licensed under the European Union Public Licence v. 1.2 (`EUPL-1.2`); the full text is in [LICENSE](LICENSE). Every released tarball and image carries a `THIRD-PARTY-NOTICES` file assembled from the reviewed inventory in [licenses/](licenses/), which covers what the artifacts actually contain: musl, the Zig runtime, SQLite, Mbed TLS and its vendored Everest and p256-m code, and the JavaScript and CSS bundled into the admin UI.