Skip to content

15. Choosing a setup

DNS is not optional, so the question is never whether to use it but how much of it to own. This chapter is the set of decisions and what each one actually costs.

MANAGED (Route 53, Cloudflare, NS1,
Google Cloud DNS, Azure DNS)
+ global anycast you cannot build
+ DDoS absorption at a scale you cannot
match
+ health-checked failover, geo and
latency routing
+ ALIAS records solving the apex problem
+ an API, and 100% SLAs that are
occasionally real
− vendor-specific record types that do
not export
− their outage is your outage
− per-query pricing at scale
− limited control over EDNS, RRL,
NSEC3 parameters
SELF-HOSTED (NSD, Knot, BIND,
PowerDNS)
+ total control: views, dynamic update,
odd record types, custom logic
+ no per-query cost
+ no dependency on a third party's
control plane
− YOU need anycast, or you have no DDoS
story at all
− you are on call for a thing that takes
the company offline
− geo-routing and health checks are
yours to build
− three servers in one datacentre is not
a DNS deployment
ONE PROVIDER
simple. one API, one source of truth,
DNSSEC works normally, ALIAS records
available.
and their control-plane outage is your
total outage.
TWO PROVIDERS, BOTH IN THE NS SET
a provider-wide failure becomes a
non-event: resolvers try the others.
COSTS
− one source of truth plus automation
(OctoDNS, DNSControl)
− no provider-specific records
− DNSSEC becomes hard: either share
a key or implement multi-signer
(RFC 8901)
− twice the surface for a
misconfiguration
THE DECIDING QUESTION
if your DNS provider is down for four
hours, what happens?
"we lose revenue continuously" → two
providers
"customers notice" → two,
if you
can
afford
the
complexity
"we post on status page" → one is
fine
"nobody notices" → one,
definitely

The 2016 Dyn attack is the reference case and the lesson held: sites with a second provider stayed reachable, and sites without one did not. The counter-lesson from the decade since is that multi-provider setups fail in their own ways — drifted records, a zone updated in one place and not the other — so they are only better if the automation is genuinely good.

USE A PUBLIC RESOLVER (1.1.1.1, 8.8.8.8,
9.9.9.9)
+ nothing to run, globally anycast,
validating, fast
− your queries are somebody's data
(policies vary and matter)
− no internal names
− no visibility for security
− ECS behaviour differs and affects CDN
routing
RUN YOUR OWN RECURSIVE RESOLVER
+ internal names, logging, policy,
filtering
+ no third party sees your queries
+ you can serve-stale and prefetch to
taste
− you must keep it up, and everything
depends on it
− cold cache after a restart
FORWARD TO A PUBLIC RESOLVER WITH A LOCAL
CACHE
+ the common middle ground. local
cache, internal zones handled
locally, recursion delegated.
− you still depend on the upstream
AND, ORTHOGONALLY: A LOCAL CACHE ON EVERY
HOST.
this is not a choice, it is a default
worth adopting in every topology above.
chapter 13.
Terminal window
# the middle ground, in Unbound
# forward everything except internal zones
cat <<'CONF'
server:
interface: 127.0.0.1
access-control: 127.0.0.0/8 allow
prefetch: yes
cache-min-ttl: 60
forward-zone:
name: "."
forward-tls-upstream: yes
forward-addr: 1.1.1.1@853#cloudflare-dns.com
stub-zone:
name: "internal.example.com"
stub-addr: 10.0.0.53
CONF
SIGN IF
− the DNS answer IS the security
decision: DANE for SMTP, SSHFP,
TLSA, anything without a browser
trust store
− you are in a sector where it is
expected or required (government,
finance in some jurisdictions,
several ccTLDs)
− your provider does it with one click
and handles key rollover
DO NOT RUSH IF
− you are HTTPS-only with HSTS, where
TLS already prevents impersonation
− you run multi-provider DNS without
multi-signer support
− your registrar handles DS updates
badly, which many do
− nobody on the team has operated it

The asymmetry from chapter 9 is the whole argument: an unsigned zone works, and a mis-signed zone is gone for 30–40% of the internet. Signing with a managed provider that automates rollover and monitors signature expiry is a reasonable, low-risk choice; signing by hand with a cron job is how domains disappear.

A DEFAULT POLICY THAT HOLDS UP
apex and www A/AAAA 300
anything behind a CDN or LB 60
MX 3600
SPF / DKIM / DMARC TXT 3600
NS at the apex 86400
SOA minimum 300
_acme-challenge 60
anything mid-migration 60
AND ONE RULE: LOWER THE TTL THREE DAYS
BEFORE YOU PLAN TO CHANGE SOMETHING.
PUBLIC example.com
INTERNAL internal.example.com
— a real subdomain of a
domain you own. not
.local, not .corp, not
.lan.
CLOUD/K8S cluster.local stays
internal to the cluster
ENVIRONMENTS api.staging.example.com,
not staging-api.example.com
— the subdomain form
delegates cleanly and
wildcards usefully
SERVICES use SRV where clients
support it; otherwise a
per-service A record
behind a load balancer
1. A MANAGED AUTHORITATIVE PROVIDER, and
sensible TTLs.
2. REGISTRAR HYGIENE: auto-renew,
transfer lock, an account email at a
different domain.
3. ZONE AS CODE, with a reviewable diff.
4. EXTERNAL MONITORING: apex resolves,
every NS agrees on the serial, expiry
> 60 days.
5. A LOCAL CACHING RESOLVER on every host
or node.
6. A SECOND PROVIDER, if four hours of
downtime is unacceptable.
7. DNSSEC, if the DNS answer is a
security decision.
8. ENCRYPTED DNS for clients, with your
own endpoint advertised via DDR.

Steps 1 to 4 cost almost nothing and prevent nearly every incident in chapter 14. Step 5 is the best latency and reliability improvement available. Steps 6 to 8 are where real commitment begins, and stopping after 5 is a completely defensible place to be.

Concrete beats abstract, so here is a configuration that is defensible for the large majority of organisations.

DOMAIN
registrar: one with transfer lock,
auto-renew, CDS support, and 2FA
account email: at a DIFFERENT domain
expiry: in a team calendar
AUTHORITATIVE
one managed provider, global anycast
zone as code in git, applied by CI
TTLs: 300 / 60 / 3600 / 86400 per
chapter 6
apex: provider ALIAS, or HTTPS alias,
or www-canonical
RESOLUTION
a local caching resolver on every host
and every node
forwarding over DoT to two public
resolvers
stub zones for internal.example.com
pointing at two internal resolvers
options timeout:1 attempts:2 rotate
NAMING
public: example.com
internal: internal.example.com
cluster: cluster.local, internal only
MONITORING, FROM OUTSIDE
apex resolves from 3 regions
all NS agree on the SOA serial
domain expiry > 60 days
no CNAME targets NXDOMAIN
(and if signed: signature validity
> 50%)
MAIL
SPF with < 10 lookups, DKIM, DMARC at
p=none → p=quarantine → p=reject, with
reports read at each step
Terminal window
# the four checks worth putting in cron
dig +short example.com @1.1.1.1 | grep -q . || alert "apex unresolvable"
for ns in $(dig +short example.com NS); do dig +short @"$ns" example.com SOA; done | awk '{print $3}' | sort -u | wc -l | grep -q '^1$' || alert "serial mismatch"
whois example.com | grep -i 'expir'
dig +short example.com MX | grep -q . || alert "no MX"

Nothing in that list is expensive, and between them they prevent essentially every incident in chapter 14. The pieces people skip are the external monitoring and the account email, and those are the two that turn a short incident into a long one.

✗ FAST FAILOVER. minutes, not seconds.
use a load balancer behind one stable
address.
✗ PRECISE LOAD DISTRIBUTION. resolvers
and client caches make weights
approximate at best.
✗ ACCESS CONTROL. a name that resolves
only internally is not a security
boundary. the address is still
reachable.
✗ A CONFIGURATION DATABASE. TXT records
holding application config is a
pattern, and it is a bad one — no
validation, no versioning, 255-octet
strings and cache delays.
✗ A SERVICE REGISTRY. it is a fine read
INTERFACE onto one (chapter 11), and a
poor store.

The access-control point is worth stating plainly because it is a common and dangerous assumption: db.internal.example.com resolving only on the corporate resolver does not stop anyone who knows 10.0.1.5 from connecting. Names are discovery, not authorisation — that is what network policy and mTLS are for.

  • For authoritative DNS, managed is the right default for almost everyone. The self-hosting failure mode is a reflection attack you cannot absorb without anycast, not slightly worse latency.
  • Self-host when you have real requirements managed cannot meet: split-horizon at scale, dynamic update from DHCP or AD, or data-residency constraints.
  • Two providers turn a provider outage into a non-event, and cost a single source of truth, automation, the loss of provider-specific records, and genuinely hard DNSSEC. Decide with one question: what happens if your provider is down for four hours?
  • For resolution, the practical middle ground is forward to a public resolver with a local cache and local stub zones for internal names. A local cache on every host is a default, not a choice.
  • Sign when the DNS answer is the security decision — DANE, SSHFP, TLSA — or when your provider automates it. Be slow when you are HTTPS-only with HSTS, multi-provider, or your registrar handles DS badly.
  • Decide TTLs once: 300 for infrastructure, 60 behind a CDN, 3600 for policy, 86400 for NS, 300 for SOA minimum — and lower them three days before a planned change.
  • Decide naming once, and never invent an internal TLD. Use internal.example.com or .internal; .corp, .home and .mail are permanently blocked for exactly this reason.
  • The adoption order is managed provider → registrar hygiene → zone as code → external monitoring → local caches → second provider → DNSSEC → encrypted DNS. The first five cost almost nothing and prevent nearly every real incident.
  • Do not ask DNS for fast failover, precise load distribution, access control, a configuration database, or a service registry. In particular, a name that resolves only internally is not a security boundary — the address is still reachable.

Next: the cheat sheet — every record type, flag, command and diagnostic from this guide on one page.

Written by Sepuri Sai Krishna

© 2026CC BY 4.0Source