Skip to content

Enable DoH and DoT

Besides plain DNS on port 53, nxdns can answer encrypted queries from your devices: DNS over HTTPS (DoH) on port 443 and DNS over TLS (DoT) on port 853. Both are off by default, and both need a certificate and its private key, as PEM files.

1. Get a certificate

Devices check the certificate against the name you give them, so use a certificate for a name you control, from an ACME client such as certbot, lego or Caddy. Android’s Private DNS setting, for example, takes a hostname and needs a certificate a phone trusts.

To try it out first, a self-signed certificate is enough:

Terminal window
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout /etc/nxdns/key.pem -out /etc/nxdns/cert.pem -days 365 \
-subj "/CN=nxdns.lan" \
-addext "subjectAltName=DNS:nxdns.lan,IP:192.168.1.10"

Replace nxdns.lan and 192.168.1.10 with the name and address your devices will use.

The key must be readable by the user nxdns runs as, and by nobody else. Under systemd that is the nxdns user:

Terminal window
chown nxdns:nxdns /etc/nxdns/cert.pem /etc/nxdns/key.pem
chmod 0644 /etc/nxdns/cert.pem
chmod 0600 /etc/nxdns/key.pem

Under Docker, put both files in etc-nxdns/ next to compose.yaml and give them to uid 65532 the same way, since the container cannot change the read-only mount itself.

2. Turn the listeners on

In file mode, add both sections to the configuration file:

.doh_server = .{
.enabled = true,
.cert_path = "/etc/nxdns/cert.pem",
.key_path = "/etc/nxdns/key.pem",
},
.dot_server = .{
.enabled = true,
.cert_path = "/etc/nxdns/cert.pem",
.key_path = "/etc/nxdns/key.pem",
},

In database mode, open System in the web interface, tick enabled under DoH Server and DoT Server, check the certificate and key paths, and save.

Both listeners bind every address on their standard ports by default; bind and port change that. The packaged systemd unit already allows binding ports below 1024. Under Docker, uncomment the 443 and 853 lines under ports: in compose.yaml.

3. Check and restart

In file mode, check loads the certificate and key the same way the listeners will:

Terminal window
nxdns check --config /etc/nxdns/config.zon

It fails when a file is unreadable or when the key does not belong to the certificate, and warns when the key is readable by others:

FAIL doh_server.key_path: '/etc/nxdns/other.pem': private key does not belong to the certificate
WARN dot_server.key_path: '/etc/nxdns/key.pem' is mode 644; a TLS key must be readable by its owner only

Then restart nxdns (systemctl restart nxdns, or docker compose restart). The log names both listeners:

info(nxdns): doh listener on 0.0.0.0:443
info(nxdns): dot listener on 0.0.0.0:853

If a certificate cannot be loaded, nxdns refuses to start and exits 2 rather than run without the listener you asked for:

doh_server: '/etc/nxdns/cert.pem' + '/etc/nxdns/key.pem': private key file is not readable
nxdns run failed: BadCertificate

4. Test them

DoT, with a dig from BIND 9.18 or newer:

Terminal window
dig @192.168.1.10 +tls example.com A +short

DoH, using curl’s own DoH resolver to look up example.com:

Terminal window
curl -sS --doh-url https://nxdns.lan/dns-query -o /dev/null \
-w 'resolved to %{remote_ip}\n' https://example.com

Add --doh-insecure to the curl command while you test with a self-signed certificate. DoH is served at /dns-query only, over HTTP/1.1, as both GET and POST.

Renewing the certificate

nxdns checks both files every 30 seconds and switches to a renewed pair on its own; connections already open finish on the old one. There is nothing to restart. Each reload is logged:

info(cert_store): certificate reloaded from /etc/nxdns/cert.pem

To load it at once instead, press Reload certificates on the System page. If a reload fails, nxdns keeps serving the certificate it has and says why.