A browser never opens a TCP socket to a brand name. It opens a socket to an address it learned from a resolver, then it authenticates that address with a certificate chain. Miss either hop and the product is down, even when every application process is healthy.
โโโโโโโ โโโโ โโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโ โโโโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโ โโโ โโโโโโโโโ โโโโโโโโโโโ โโโ โโโ โโโโโโโโ โโโ โโโโโโโโโโโโโโโโโโโโโ โโโ โโโ โโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโโโโ โโโ โโโโโโโโโโโโโโโโ โโโโโโโ โโโ โโโโโโโโโโโโโ โโโ โโโโโโโโโโโโโโโโ
DNS answers "which address, which service parameters." TLS answers "is this the name I asked for, and can we speak without eavesdroppers." They are independent protocols. Production treats them as one path because a client cannot start TLS until it has a destination, and a correct destination is useless if the handshake fails.
The application only sees a hostname. The stack underneath walks: stub resolver โ recursive resolver โ authoritative servers โ cached answer โ TCP or QUIC connect โ TLS handshake โ application bytes.
CLIENT
stub resolver (OS / libc / getaddrinfo)
โ UDP/53 or DoT:853 or DoH:443
โผ
RECURSIVE RESOLVER (ISP, 1.1.1.1, 8.8.8.8, corporate)
cache? โ return
else walk:
root โ TLD โ authoritative
โ
โผ
ANSWER: A / AAAA / CNAME / HTTPS / SVCB + TTL
โ
โผ
TRANSPORT TCP:443 or QUIC:443
โ
โผ
TLS 1.3 ClientHello (SNI) โ ServerHello โ EncryptedExtensions
Certificate + CertificateVerify โ Finished
โ
โผ
APPLICATION HTTP/2 or HTTP/3
If DNS or TLS is wrong, your deploy is irrelevant. Health checks that only ping the origin miss both layers. Probe the name from the public resolver set your users actually use, then complete a TLS handshake with the same SNI the browser sends.
The stub on the device is deliberately thin. It forwards a query and waits. The recursive resolver does the work: it starts at a root hint, follows NS delegations, and caches every useful RRset it sees. Authoritative servers own a zone. They do not recurse.
Mixing those roles is how you get loops and cache poisoning surfaces. A nameserver that is authoritative for example.com should not also recurse for random clients on the same socket.
flowchart TD
A[Stub resolver] -->|QNAME QTYPE| B{Recursive cache}
B -->|HIT TTL live| C[Return RRset]
B -->|MISS| D[Query root]
D --> E[TLD NS referral]
E --> F[Authoritative NS]
F -->|A AAAA HTTPS| B
C --> G[Client connect]
F -->|NXDOMAIN| H[Negative cache]
A 30-second A record sounds agile until every recursive resolver on earth still holds the previous answer. A 24-hour TTL sounds cheap until you need to fail away from a dead anycast. Pick TTLs per record class: long for NS and MX that almost never move, short for A/AAAA that sit in front of a failover, longer for CAA that you rotate on a calendar.
Resolvers may clamp TTLs. Some recursive operators raise very low TTLs. Do not design a cutover that requires every cache on the internet to honor a 5-second record.
example.com. 3600 IN SOA ns1.example.com. hostmaster.example.com. (
2026082601 7200 3600 1209600 300 )
example.com. 86400 IN NS ns1.example.com.
example.com. 86400 IN NS ns2.example.com.
example.com. 3600 IN CAA 0 issue "letsencrypt.org"
example.com. 3600 IN CAA 0 issuewild ";"
www.example.com. 60 IN A 203.0.113.10
www.example.com. 60 IN AAAA 2001:db8::10
www.example.com. 300 IN HTTPS 1 . alpn="h3,h2"
You cannot put a CNAME at the apex next to SOA and NS. Providers fake it with ANAME, ALIAS, or flattening: they resolve the target server-side and serve A/AAAA. Know which you have. Flattening hides the target TTL and can delay failovers by whatever interval the provider re-resolves.
DNSSEC signs RRsets so a validating resolver can detect tampering. It does not encrypt the query. A passive observer on UDP/53 still sees QNAMEs. Confidentiality is DoT or DoH. Authenticity is DNSSEC. They solve different attacks.
The chain is: DNSKEY at the zone, RRSIG over each signed RRset, DS at the parent that pins the child key. A break in that chain is indistinguishable from "unsigned" to a validator that requires a secure path. Half-deployed DNSSEC is worse than none: a broken DS takes the name off the internet for validating resolvers.
| Property | Plain DNS | DNSSEC | DoT / DoH |
|---|---|---|---|
| Integrity of answer | None (spoofable) | Signed RRsets | Channel integrity only |
| Query privacy | Cleartext | Cleartext | Encrypted to the resolver |
| Failure mode | Wrong answer possible | SERVFAIL on bad chain | Timeout if resolver unreachable |
DNS over TLS (RFC 7858) wraps the DNS message in TLS on port 853. DNS over HTTPS (RFC 8484) carries it as an HTTP exchange on 443, usually application/dns-message. Both hide QNAMEs from the local network. Neither hides them from the recursive operator you chose.
Enterprise networks that intercept UDP/53 lose visibility when the stub speaks DoH to a public resolver. That is the policy fight. Architecturally: pick one recursive trust anchor per environment (device, office, CI) and pin it. Split-horizon DNS and DoH to a public resolver will leak internal names or return the wrong public answer.
Whoever recurses for you sees every name the fleet resolves: staging hosts, partner admin panels, internal package registries if they leak. Treat the recursive resolver like a log sink. Do not point production stubs at a random public IP because a tutorial did.
RFC 8446 cut a round trip and hid certificates from the path. The ClientHello is still visible: protocol version, cipher suites, key share, ALPN, and Server Name Indication. A middlebox that wants to route or filter on hostname still can, unless Encrypted Client Hello is negotiated end to end.
TLS 1.2 remains on old stacks. New work should be 1.3 only. 1.0 and 1.1 are dead. If a vendor requires 1.0, that is a vendor problem, not a reason to reopen the protocol on your edge.
sequenceDiagram participant C as Client participant S as Server C->>S: ClientHello (SNI, key_share, ALPN) S->>C: ServerHello (key_share) S->>C: EncryptedExtensions S->>C: Certificate + CertificateVerify S->>C: Finished C->>S: Finished C->>S: Application data (HTTP)
ssl_protocols TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_certificate /etc/ssl/example.fullchain.pem;
ssl_certificate_key /etc/ssl/example.key;
ssl_stapling on;
ssl_stapling_verify on;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
Leaf certificate names the host (SAN, not CN in modern clients). Intermediate is signed by a root in the client's trust store. Serve leaf plus intermediates. Do not serve the root. Missing intermediates are the "works on my laptop, fails on mobile" special: the laptop cached the intermediate from another site.
ACME (RFC 8555) is how Let's Encrypt and other CAs issue without a human in the loop. HTTP-01 proves control of port 80. DNS-01 proves control of a TXT record and is the only sane path for wildcards and locked-down origins.
Most TLS outages are not novel cryptanalysis. They are a leaf that expired because the ACME hook could not write the zone, the reload never ran, or the monitor checked the origin certificate while users hit a different edge cert. Alert on notAfter from the public name, from the same vantage as your users.
| Symptom | Look at first | Do not look at first |
|---|---|---|
| Users cannot resolve the name | Public recursive, NS, SOA, registrar | Application pods |
| Name resolves, handshake fails | SNI, chain, notAfter, cipher, ALPN | HTTP handler code |
| Some networks work, others fail | Split horizon, DoH policy, IPv6, MTU | A single region deploy |