mokhtar 35f23240e7
CI / test (push) Failing after 48s
CI / test-aarch64 (push) Failing after 2m25s
CI / frontend (push) Successful in 45s
CI / cross (push) Failing after 22s
CI / docker (push) Failing after 18s
fix blocklist fetcher aborting the process on a buffered read
the response reader was constructed over transfer_buf and then read into that
same buffer, so readSliceShort memcpy'd the reader's buffer onto itself and
panicked on any read that found bytes already buffered. stream the body
straight into the caller's writer instead: no second copy, no aliasing.
2026-08-02 16:47:14 +02:00

nxdns

A self-hosted DNS sinkhole for a household LAN, written in Zig 0.16. One static musl binary, SQLite for state, a Raspberry Pi 5 as the reference target. It answers your network's DNS, blocks what you tell it to, and shows you what asked for what.

Features

  • Blocklist filtering: subscribe to hosts/domain lists, plus your own allow and block rules with wildcard support (*.example.com)
  • 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

Quickstart (docker compose)

Build the binary and image, seed a minimal configuration, start it:

(cd web && npm ci && npm run build)
zig build cross -Dweb-dist=web/dist -Doptimize=ReleaseSafe

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
docker compose up -d

DNS is on port 53, the web UI on http://localhost:8080. The config file seeds the database on first boot only; from then on the database is the truth and changes go through the UI, the API, or nxdns export / nxdns import. Full install instructions, including the systemd path and the Pi 5 recipe, are in docs/operator.md.

Building from source

Requires Zig 0.16.0 and Node.js 24 (for the web UI). C dependencies (SQLite, mbedTLS) are vendored and built by zig build.

(cd web && npm ci && npm run build)          # web UI -> web/dist
zig build -Dweb-dist=web/dist                # native binary -> zig-out/bin/nxdns
zig build cross -Dweb-dist=web/dist -Doptimize=ReleaseSafe
                                             # static x86_64 + aarch64 musl binaries
zig build test --summary all                 # unit tests

Documentation

S
Description
No description provided
Readme EUPL-1.2
2.4 MiB
v0.0.1
Latest
2026-08-08 23:26:44 +00:00
Languages
Zig 89.5%
TypeScript 9.8%
JavaScript 0.4%
Dockerfile 0.1%