nxdns

;; nxdns.sh

A DNS sinkhole for your LAN

It blocks the names you do not want, and forwards the rest over an encrypted connection.

;; ANSWER SECTION

Every answer, with its reason

The admin UI records which rule, list or upstream decided each query, and keeps that record after the rule changes.

Activity: every query with its client, result and route.

Activity screen of the nxdns admin UI, light themeActivity screen of the nxdns admin UI, dark theme

;; VERIFY

Check what you run

Every release is signed, and the tarballs rebuild to the same bytes. CI rebuilds them before upload and stops the release if one byte differs.

$ gpg --verify SHA256SUMS.txt.asc SHA256SUMS.txt
gpg: Good signature from "Mokhtar Mial (pc) <…>"
Primary key fingerprint: A206 1F6A B24D F2C0 E923  46FD 1509 B549 46D0 8A95
$ sha256sum -c --ignore-missing SHA256SUMS.txt
nxdns-0.0.28-x86_64-linux-musl.tar.gz: OK

;; FEATURES

Small on purpose

Built for one household and the devices in it, on a Raspberry Pi or any Linux machine.

  1. Filtering

    Hosts files, plain domain lists and Adblock Plus lists, plus your own allow and block rules. Each answer records the rule that decided it.

  2. Config as code

    Point --config at one ZON file in git. nxdns converges onto it at every start, and the admin UI refuses edits that would drift from it.

  3. DoH and DoT

    Forwards over HTTPS or TLS with failover between upstreams, and serves DoH and DoT to your own devices.

  4. Per-client groups

    Give the kids’ phone its own lists and safe search. Every other device keeps the default.

  5. Metrics

    Prometheus metrics at /metrics for queries, the cache and each upstream.

  6. Query log

    Searchable history in SQLite, pruned after 30 days by default, so the disk use stays bounded.

;; COMPARISON

How it compares

To help you choose. nxdns has no DHCP server and does not validate DNSSEC; if you need either, pick a tool that does.

nxdns0.0.28Pi-hole6.5 (FTL 6.8)AdGuard Home0.107.79Technitium15.6.2
How it shipsStatic binary with the web interface included; archive, Docker or NixServer with separate web files and command-line tools; installer or DockerBinary with the web interface included; installer, archive, Docker or SnapNeeds the ASP.NET Core runtime; installer, archive or Docker
Resource use on small machinesLimits simultaneous queries and cache size; threads can grow, and list updates keep old and new lists in memoryBlocklists stay in a database file; the system can reclaim memory used to read it, and updates run separatelyLimits simultaneous queries; list updates build new filters in memory while the old ones remain activeUses .NET; tuned to add threads readily when busy
Config as a file in git, applied at every startYes--configPartialSettings in pihole.toml, which can be locked read-only; lists and rules are in a databasePartialA running instance overwrites the YAML fileNoA binary file that the app writes
Encrypted upstreams, built inYesDoH, DoTNoNeeds separate software, such as dnscrypt-proxyYesDoH, DoT, DoQ, DNSCryptYesDoH, DoT, DoQ
DoH and DoT for your devicesYesNoYesAlso DoQ, DNSCryptYesAlso DoQ
Signed, reproducible releasesYesBothNoChecksums onlyPartialSigned, with a published key; reproducibility not documentedNoChecksums only
DHCP serverNoYesYesNot on WindowsYes
DNSSEC validationNoYesOff by defaultNoYes

checked 2026-10-30 against the versions named in each column

;; INSTALL

Run it on your network

Pick one way to run it. Each runs the same signed release, and the Docker image pulls without an account.

mkdir -p ~/nxdns/etc-nxdns && cd ~/nxdns
curl -O https://git.mial.net/mokhtar/nxdns/raw/tag/v0.0.28/deploy/docker/compose.yaml
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

Then point your router’s DNS at this machine. The admin UI is on port 8080.Install with Docker

curl https://keys.openpgp.org/vks/v1/by-fingerprint/A2061F6AB24DF2C0E92346FD1509B54946D08A95 | gpg --import
curl -O https://git.mial.net/mokhtar/nxdns/releases/download/v0.0.28/nxdns-0.0.28-x86_64-linux-musl.tar.gz
curl -O https://git.mial.net/mokhtar/nxdns/releases/download/v0.0.28/SHA256SUMS.txt
curl -O https://git.mial.net/mokhtar/nxdns/releases/download/v0.0.28/SHA256SUMS.txt.asc
gpg --verify SHA256SUMS.txt.asc SHA256SUMS.txt
sha256sum --check --ignore-missing SHA256SUMS.txt
tar -xzf nxdns-0.0.28-x86_64-linux-musl.tar.gz

Then install the binary and the systemd unit from the extracted directory, and write the config.Install with systemd

# flake.nix
inputs.nxdns = {
  url = "git+https://git.mial.net/mokhtar/nxdns.git?ref=refs/tags/v0.0.28";
  inputs.nixpkgs.follows = "nixpkgs";
};

# configuration.nix
{ inputs, ... }:
{
  imports = [ inputs.nxdns.nixosModules.default ];
  services.nxdns = {
    enable = true;
    configFile = ./nxdns-config.zon;
    openFirewall = true;
  };
}

Then point your router’s DNS at this machine. The guide shows what goes in the config file.Install with Nix