What A and AAAA records do
An A record says “this name lives at this IPv4 address”. An AAAA record says the same for IPv6. When someone opens www.example.com, their resolver asks for both, and the client connects to whatever comes back.
That’s the whole mechanism, and most problems come from one of three gaps between what the records say and reality:
- there is no record where the client is looking, or it is in a zone nobody queries;
- the record points at an address where the site isn’t (an old server, a parking page, a private IP);
- the AAAA record exists but the server doesn’t actually answer over IPv6.
The last one is the sneakiest, because browsers hide it.
Check what the records really are
Ask for each type explicitly. dig without a type asks for A only, and plenty of “my IPv6 is fine” conclusions come from never asking for AAAA.
dig +short A example.com
dig +short AAAA example.com
dig +short A www.example.com
dig +short AAAA www.example.com
The apex (example.com) and www are separate names with separate records. Check both, and every subdomain that people actually use.
If a name is a CNAME, +short prints the chain and then the address:
$ dig +short A www.example.com
example.netlify.app.
203.0.113.40
The full output tells you more than +short, mainly the TTL and the status line:
$ dig A example.com
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41312
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; ANSWER SECTION:
example.com. 3600 IN A 203.0.113.10
3600 is the remaining cache time in seconds at the resolver you asked. The DNS lookup shows A, AAAA and the other common types side by side from outside your network, which is handy when your own resolver may be holding an old answer.
“No A record”: NXDOMAIN or NODATA?
These look the same in a browser and mean different things.
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, ...
NXDOMAIN means the name doesn’t exist at all. Either it was never created, it’s a typo, or the whole domain isn’t delegated (expired, on hold, nameservers broken).
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, ...
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
;; AUTHORITY SECTION:
example.com. 900 IN SOA ns1.dnshost.example. hostmaster.example.com. ...
NOERROR with zero answers (often called NODATA) means the name exists but has no record of that type. Typical cases: the name has only an AAAA, only MX or TXT records, or the apex has a record at the DNS host but nobody added an A record for it.
For NXDOMAIN on a whole domain, check the registration side first with an RDAP lookup: an expired domain or one with clientHold drops out of the TLD zone, and no record you add will fix that.
Ask the authoritative nameserver directly
Before changing anything, find out whether the record is missing at the source or only in caches.
dig +short NS example.com
dig @ns1.dnshost.example A www.example.com +norec
+norec asks the authoritative server for its own data without recursion. Three outcomes:
- The authoritative server has the right record, your resolver doesn’t. It’s caching. Wait for the old TTL to run out. If you looked the name up before the record existed, resolvers also cache that negative answer, for up to the zone’s negative TTL (the smaller of the SOA record’s TTL and its last field). That’s why “I just added it and it still says no record” can last a while.
- The authoritative server doesn’t have it either. The record was never saved, or was saved under the wrong name. A classic: typing
www.example.cominto a panel that appends the zone, creatingwww.example.com.example.com. - The NS records point somewhere other than where you edited. You added the record at your registrar’s DNS, but the domain is delegated to Cloudflare, or the other way round. Every edit at the wrong provider is invisible. See nameserver mismatch.
Wrong address: the record exists but points at the wrong place
Compare the IP in DNS with the IP the server actually has. On a VPS, ip -4 addr and ip -6 addr show local addresses, but behind NAT or a cloud load balancer the public address is different: use the one the provider’s console shows.
The usual wrong values:
An old server after a migration. The A record still says the previous host’s IP. Visitors get the old site, or a certificate error if the old host no longer has your certificate.
A parking or default address. Many registrars and hosts create default A and AAAA records when a domain is registered. You replace the A record with your server’s IP and leave the AAAA pointing at the registrar’s parking page. IPv4 visitors see your site; IPv6 visitors see “this domain is parked”.
A private address. 10.x.x.x, 172.16–31.x.x, 192.168.x.x or fd00::/8 in a public zone. It works inside the office and nowhere else. If you need different answers inside and outside, that’s split-horizon DNS, and the private value belongs only in the internal zone.
A CDN or proxy in front you forgot about. With Cloudflare’s proxy on, public DNS returns Cloudflare’s addresses, not yours. That’s expected; the origin IP lives in the Cloudflare dashboard.
AAAA published, IPv6 broken
This deserves its own section because it’s both common and hard to notice.
When a name has both A and AAAA records, clients with IPv6 connectivity generally try IPv6 first. Modern browsers use Happy Eyeballs (RFC 8305): if the IPv6 connection doesn’t succeed within a short delay, a fraction of a second, they start an IPv4 attempt in parallel and use whichever connects. So in a browser, a dead AAAA costs a barely noticeable delay and nothing else. You’ll never see it.
Other clients aren’t so forgiving. Anything that walks the address list one at a time, with a full connect timeout per address, sits on the dead IPv6 address for seconds before trying IPv4, or gives up. That includes plenty of scripts and libraries (Python’s requests, for example, and wget), monitoring agents, and webhook senders in other companies’ infrastructure. Let’s Encrypt’s HTTP-01 validation prefers IPv6 when an AAAA record exists; if the IPv6 address answers with a different server’s content, validation fails instead of retrying over IPv4.
The symptoms are therefore odd: “works in Chrome, times out in our integration”, “certificate renewal keeps failing”, “the uptime monitor flaps”.
Testing it
Force each protocol with curl:
curl -4 -sS -o /dev/null -w '%{http_code} %{remote_ip}\n' https://www.example.com/
curl -6 -sS -o /dev/null -w '%{http_code} %{remote_ip}\n' https://www.example.com/
If -4 returns 200 and -6 hangs, gets refused, or returns a different status, the AAAA is broken.
One trap: run this from a machine that has working IPv6 itself. On a network without IPv6, curl -6 fails with “Network is unreachable” or “Couldn’t connect” for every site, which tells you nothing about yours. A quick sanity check is curl -6 -sI https://www.google.com/. If that fails too, test from a VPS with IPv6.
The usual causes and fixes
The web server doesn’t listen on IPv6. In nginx, listen 80; and listen 443 ssl; bind IPv4 only. You need separate lines:
listen 443 ssl;
listen [::]:443 ssl;
Check what’s listening with ss -ltnp | grep -E ':(80|443)\b'. You want to see [::]:443 (or *:443), not only 0.0.0.0:443. nginx -T | grep listen shows every listen directive across the config, including virtual hosts that only got the IPv4 line.
The firewall only allows IPv4. iptables and ip6tables are separate rule sets; opening 443 in one doesn’t open it in the other. With nftables an ip family table covers IPv4 only, while inet covers both. Cloud firewalls are the same: an AWS security group rule for 0.0.0.0/0 doesn’t cover ::/0.
The AAAA points at a host that isn’t yours. The parking case above, or an old server’s IPv6 left behind after a move. Compare the AAAA value with ip -6 addr on the server.
The server has IPv6 but no route. Some providers assign an IPv6 address that only works after you configure the gateway, or not at all on certain plans. If ping -6 2001:4860:4860::8888 fails from the server, the server itself has no working IPv6.
If you can’t make IPv6 work right now, delete the AAAA record. A site reachable only over IPv4 is fine. A site with an AAAA record that leads nowhere is worse than no AAAA.
Multiple A records
Several A records on one name is plain DNS round robin. Resolvers return all of them, often in rotating order, and each client picks one. There’s no health check: DNS keeps handing out an address whether the server behind it is up or not.
Browsers do try the next address if a connection fails outright, so a dead server often shows up as slowness rather than a hard failure. Other clients may just fail.
The common accidental version: during a migration someone adds the new server’s IP instead of replacing the old one. Now some visitors reach the new server and some the old one, changes “only sometimes” appear, and logins break because sessions live on one machine. List every address:
dig +short A www.example.com
If you see an address you don’t recognise, test it directly:
curl -sI --resolve www.example.com:443:198.51.100.7 https://www.example.com/
--resolve forces curl to connect to that IP while sending the real hostname, so you see exactly what a visitor routed there would see. Remove the stale record, then wait out its TTL before switching off the old server.
If you want several servers on purpose, round robin works for spreading load across identical machines, but it’s not failover. For that you need a load balancer or a DNS provider with health checks.
Changing a record: TTL decides how long the old one lives
Resolvers that already cached the old answer keep it until its TTL expires. A record with TTL 86400 can take up to a day to change everywhere, no matter how fast you edit.
For a planned move:
- A day or more ahead (at least one old TTL), lower the TTL to 300.
- Change the A and AAAA values together. Leaving one behind is how the IPv6 parking problem happens.
- Watch public resolvers with the propagation check until they agree.
- Keep the old server running until then, and raise the TTL again afterwards.
The reasoning behind specific values is in TTL best practices, and the “why do some people still see the old site” question in DNS propagation.
If you’re a visitor and only you can’t reach the site
If the site works for others, the stale answer is probably on your machine or network:
- Compare your resolver with a public one:
nslookup www.example.comversusnslookup www.example.com 1.1.1.1. - Check the hosts file (
/etc/hosts, orC:\Windows\System32\drivers\etc\hosts) for an old entry. - Flush caches. Windows:
ipconfig /flushdns. macOS:sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder. Chrome keeps its own cache atchrome://net-internals/#dns.
The browser error you see narrows it down: ERR_NAME_NOT_RESOLVED and DNS_PROBE_FINISHED_NXDOMAIN are lookup failures; a timeout after a successful lookup is a connection problem, not DNS.
Related
- CNAME vs A record: when to alias instead, and why the apex can’t be a CNAME.
- Nameserver mismatch: when your edits never reach the servers the world asks.
Check your own domain now
Free, no sign-up. Runs the exact check this guide describes and shows what to fix.