Network access
A node answers your own network and nobody else, by default. That way a node that ends up reachable from the internet (a port forward on the router, a server with a public address) is neither an open resolver, which attackers use for DNS amplification, nor an exposed admin page.
Who may query the node
Section titled “Who may query the node”Point a device’s DNS server setting at the node’s LAN IP address. An unidentified client uses the node’s default profile, including its filtering rules, over both UDP and TCP; it does not need a profile ID in the query. The same default applies to encrypted queries without a profile identity.
The DNS listeners answer these sources, plus anything in dns.allow_from:
| Range | What |
|---|---|
10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 |
private IPv4 networks (RFC 1918) |
100.64.0.0/10 |
shared address space: carrier-grade NAT, Tailscale |
127.0.0.0/8, ::1 |
the machine itself |
169.254.0.0/16, fe80::/10 |
link-local |
fc00::/7 |
IPv6 unique local addresses (ULA) |
Any other source gets:
- on plain DNS (port 53, UDP and TCP):
REFUSEDwith Extended DNS Error 18 (Prohibited), before any lookup and without a log record; - on DoT, DoH and DoQ: the connection is closed after the handshake.
Add any other network you trust the same way: a VPN range, a second site,
a public address you own. Each entry is a CIDR or a single address. 0.0.0.0/0
or ::/0 turns the guard off for that address family; the node then warns
on its page and in opdns-node status that it is an open resolver.
dns.public_transports: true lets any source use the encrypted
listeners (DoT, DoH, DoQ, DoH3) while plain DNS on port 53 stays guarded,
for a node you deliberately use from outside your network, such as a phone
on mobile data. The page warns while it is on.
The page’s lan guard row shows whether the guard is on and how many queries it refused in the last hour. Over 100 in an hour puts a warning on the page: the node is probably reachable from the internet, so check the router’s port forwarding. The node’s log says the same, at most once a minute, with the source address.
DoT and DoH with your own hostname
Section titled “DoT and DoH with your own hostname”To use encrypted DNS on your LAN, configure a hostname that resolves to the node’s LAN address and a certificate whose Subject Alternative Name (SAN) contains that hostname. For example, replace the address and paths below with your node’s address and PEM certificate chain/private key:
listen: udp: ["192.168.1.2:53"] tcp: ["192.168.1.2:53"] dot: ["192.168.1.2:853"] doh: ["192.168.1.2:443"] dns_domain: dns.home.arpa tls_cert: /etc/opdns/tls/dns.home.arpa.crt tls_key: /etc/opdns/tls/dns.home.arpa.keyThe node must be able to read the certificate chain and private key.
Restart the node after changing listener addresses or dns_domain. Renewed
certificate contents at the same paths are reloaded automatically. These binds
serve only the chosen LAN address; add your LAN IPv6 address explicitly if
needed, along with its allowed source prefix as described above. See the
configuration reference for all
listen keys and configuration validation.
Configure a DoT client with server hostname dns.home.arpa, port 853,
and the node’s address. For DoH, use
https://dns.home.arpa/dns-query (port 443). Neither needs a profile ID
in the hostname or URL to use the node’s default profile. Clients must
trust the certificate’s issuing CA and verify dns.home.arpa against its
SAN; install your local CA on the client if you use one. Clients may dial
the node’s IP while verifying dns.home.arpa. Using the
IP as the TLS server name or HTTPS URL host requires a matching IP SAN in
the certificate. Do not turn
off certificate verification to make it connect.
These DNS TLS settings are separate from the local page’s web.hosts
allowlist below.
The local page and API
Section titled “The local page and API”The page on port 8053, /api/status, /healthz, /metrics (when
turned on) and the
standalone local API have their
own allowlist, with the same defaults, extended by web.allow_from. Other
sources get 403.
They also check the Host header, which stops a web page you visit from
reaching the node through DNS rebinding. Accepted: an IP address,
localhost, the node’s name and the machine’s host name (each also with
.local), and the names in web.hosts. Any other name gets
421 Misdirected Request. If you open the page by a name of your own, such
as dns.home.arpa, add it:
web: hosts: [dns.home.arpa]A password on the page
Section titled “A password on the page”Anyone on your network can open the page by default. To ask for a password, hash one and put the hash in the configuration:
opdns-node hash-password # reads one line from standard input: the password, 8 characters or moreweb: password_hash: "$argon2id$v=19$m=65536,t=3,p=2$..."The page, /api/status and /metrics then ask for it (HTTP Basic: any
user name, that password). /healthz stays open for health checks, and
the standalone local API keeps its own bearer token. Each source gets five
password attempts a minute; more are answered 429. A reload
(systemctl reload opdns-node) applies a new hash without a restart, and
config check and --print-config print it as <redacted>. The page is
plain HTTP: the password keeps other people on your network out, but it
crosses the network unencrypted.
Metrics
Section titled “Metrics”/metrics (Prometheus) is off by default. page.metrics: true
(OPDNS_PAGE_METRICS=true) serves it on the page’s port, behind the same
allowlist and, when set, the password. It has the resolver’s own metrics
plus the node’s: link state, log backlog, SQLite size, writer queue,
dropped records, relayed queries shed under load
(opdns_node_queries_shed_total) and queries refused by the network guard
(opdns_node_open_resolver_refused_total). It shows the versions the node
runs, which is why it is opt-in.
The keys
Section titled “The keys”| Key | Environment variable | Default |
|---|---|---|
dns.allow_from |
OPDNS_DNS_ALLOW_FROM |
none beyond the ranges above |
dns.public_transports |
OPDNS_DNS_PUBLIC_TRANSPORTS |
false |
web.allow_from |
OPDNS_WEB_ALLOW_FROM |
none beyond the ranges above |
web.hosts |
OPDNS_WEB_HOSTS |
none beyond the names above |
web.api_token |
OPDNS_WEB_API_TOKEN |
generated into <data_dir>/secrets/api_token |
web.password_hash |
OPDNS_WEB_PASSWORD_HASH |
none: no password |
page.metrics |
OPDNS_PAGE_METRICS |
false |
dns.allow_from, web.allow_from, web.hosts and web.password_hash
take effect on a reload (SIGHUP), without a restart.
Lists in environment variables are comma separated. All keys are in the configuration reference.
Symptoms
Section titled “Symptoms”| Symptom | Cause |
|---|---|
a device gets REFUSED (EDE 18) from the node |
its address is outside the allowed ranges: a global IPv6 address, or a network you did not add to dns.allow_from |
the page answers 403 |
you opened it from outside the allowed ranges; add your network to web.allow_from |
the page answers 421 |
you opened it by a name the node does not know; use its IP address or add the name to web.hosts |
| “Refused … queries from non-LAN sources in the last hour” | port 53 is reachable from the internet; remove the port forward |