TL;DR: If IPv4 works but IPv6 users time out, the fastest fix is almost always to delete the broken AAAA record (a dead AAAA is worse than no AAAA — Happy Eyeballs falls back from “no AAAA” cleanly, but burns time on a “bad AAAA”). Confirm the break with curl -6 -I https://yourdomain.com (hangs) vs curl -4 -I https://yourdomain.com (works), then either remove the stale AAAA or fix the origin’s IPv6 path. Details below.
A user from a mobile carrier or a modern residential ISP reports your site won’t load — the connection times out at the TCP level. You test from your laptop on cable internet and it loads instantly. The difference is IPv6: mobile carriers (T-Mobile, Verizon Wireless, Vodafone, many APAC carriers) deliver IPv6-only or IPv6-preferred connectivity, and the browser’s Happy Eyeballs algorithm tries the AAAA record first. If your AAAA points to a dead host, exists where a CDN-fronted setup never expected one, or your origin’s firewall drops IPv6 packets, that user waits the full TCP timeout (often 75-120s) before maybe falling back to IPv4 — by which time they have closed the tab. This is invisible to most monitoring because IPv4 health checks pass.
One nuance that surprises people: a working absence of AAAA is fine, but a broken AAAA is the worst case. Under Happy Eyeballs v2 (RFC 8305), a client that gets both an A and an AAAA starts the IPv6 attempt first and, if it has not connected, starts the IPv4 attempt after the Connection Attempt Delay — a recommended 250 ms as of June 2026. If there is no AAAA at all, the client never wastes time on v6. So “delete the bad record” genuinely fixes the symptom; “add a half-working record” does not.
Which bucket are you in?
Run these two commands first, then read the table.
dig AAAA yourdomain.com +short # is there an AAAA at all?
curl -6 -I https://yourdomain.com # does v6 actually reach you?
dig AAAA returns | curl -6 result | Most likely cause | Jump to |
|---|---|---|---|
| nothing | (n/a) | No AAAA at all — fine, unless mobile users still complain (see NAT64) | Cause 6 |
| an address | connection refused / hangs at TCP | Origin firewall or proxy not listening on v6 | Cause 3 / 4 |
| an address | wrong site / cert mismatch | AAAA points past the CDN to a bare origin | Cause 2 |
| an address | times out, A points elsewhere | Stale AAAA from an old host | Cause 1 |
| an address | works, but users still report slowness | Old bad AAAA still cached at high TTL | Cause 5 |
Common causes
Ordered by what causes the most observable user pain.
1. AAAA record exists but points at an IP that no longer hosts you
You used to host on a server with both A and AAAA. You migrated to a new host (Vercel, Netlify, a different VPS) and updated the A record but forgot AAAA. The old IPv6 address is dead or now belongs to someone else.
How to spot it: dig AAAA yourdomain.com +short returns an address, but curl -6 -I https://yourdomain.com times out or returns wrong content.
2. CDN front exposes IPv4 only, AAAA pointing past it
Your CDN (older Cloudflare config, certain Bunny setups, custom edge) only returns IPv4 in its IP list, but you manually added an AAAA pointing at your origin to “help IPv6 users”. Those users bypass the CDN entirely and hit a misconfigured / unreachable origin.
How to spot it: Your A record returns CDN IPs. Your AAAA returns origin IP. curl -6 hits the origin directly without CDN protection.
3. Origin firewall drops IPv6 packets
ip6tables (or the cloud provider’s security group) is not configured. The default deny rule blocks inbound IPv6 on :443. Origin is technically dual-stack-capable but no one can reach it over v6.
How to spot it: sudo ip6tables -L INPUT -v -n shows no ACCEPT rule for :443 over v6. From outside: nc -6 -zv yourdomain.com 443 hangs.
4. Reverse proxy / nginx not listening on v6
Even if the OS firewall is open, nginx defaults to listen 443 ssl; which is IPv4-only. You need listen [::]:443 ssl; explicitly.
How to spot it: ss -tlnp | grep :443 shows 0.0.0.0:443 but no [::]:443. Or shows both but only IPv4 is bound.
5. Happy Eyeballs falls back, but the AAAA TTL is too high so it stays cached
Even after you remove a bad AAAA, browsers cache it for TTL duration (often 4-24h). Users keep hitting v6 first, timing out, falling back to v4 — slow site experience for hours after the “fix”.
How to spot it: User says “the site eventually loads but takes 30s every time”. Browser DevTools timing shows long DNS / connection delay before TCP success.
6. ISP NAT64 / CLAT introduces SSL SNI issues
Mobile carriers using NAT64 (no native v4 to the device) synthesize AAAA from your A record. If your SNI / Host header logic depends on the original source family, it breaks under NAT64.
How to spot it: Your A is fine, you have no AAAA, but mobile carrier users still complain. SNI gets a well-known-ipv6-pref synthetic. Check carrier-specific.
Before you start
- Identify which users / regions are affected — mobile carriers vs residential vs corporate networks all behave differently.
- Confirm your hosting platform’s IPv6 stance: does it expose AAAA? Cloudflare yes by default, raw VPS depends on config.
- Have an IPv6-capable client to test with.
test-ipv6.comconfirms your client has working v6. - Know your origin server’s IPv6 address (cloud provider control panel shows it).
Information to collect
- Output of
dig A yourdomain.com +shortanddig AAAA yourdomain.com +short. - Output of
curl -4 -I https://yourdomain.comANDcurl -6 -I https://yourdomain.com. - Output of
ss -tlnp | grep :443on the origin. - Output of
ip -6 addr showon the origin (shows assigned v6 addresses). - A test from an IPv6-only / IPv6-preferred network — mobile data is the easiest source.
Step-by-step fix
Order: kill the bad AAAA fastest, then add proper dual-stack.
Step 1: Test both stacks from outside
curl -4 -v https://yourdomain.com 2>&1 | head -20
curl -6 -v https://yourdomain.com 2>&1 | head -20
If -4 succeeds and -6 hangs or fails, the diagnosis is confirmed. Note WHERE -6 fails: at TCP connect, at TLS handshake, at HTTP response — each implies a different layer broken.
Step 2: Remove a dead AAAA record
If dig AAAA yourdomain.com returns an address that doesn’t respond:
- DNS dashboard → find the AAAA record.
- Delete it (or update to the correct address).
- Lower the TTL of nearby records to 300s temporarily so future changes propagate fast.
Browsers and resolvers will start ignoring v6 for your domain within the old TTL. Happy Eyeballs falls back to v4 cleanly.
Step 3: For dual-stack origins, ensure the server listens on v6
Edit /etc/nginx/sites-available/yourdomain (or wherever):
server {
listen 443 ssl;
listen [::]:443 ssl ipv6only=on;
http2 on;
server_name yourdomain.com;
# ...
}
The [::]:443 line binds IPv6. Two things changed in modern nginx that trip people up:
- Since nginx 1.25.1,
http2is its own directive (http2 on;), not a flag onlisten. Both styles work on most builds, but the standalone directive is current as of June 2026. ipv6only=onis the default for alisten [::]socket (since nginx 1.3.4) and nginx sets theIPV6_V6ONLYsocket option explicitly, overriding the kernelnet.ipv6.bindv6onlysysctl. That means[::]:443accepts IPv6 only — you genuinely need the separatelisten 443 ssl;line for IPv4. Do not assume one socket covers both families.
Restart:
sudo nginx -t && sudo systemctl reload nginx
ss -tlnp | grep :443
Output should now show both 0.0.0.0:443 and :::443.
Step 4: Open IPv6 firewall
sudo ip6tables -A INPUT -p tcp --dport 443 -j ACCEPT
sudo ip6tables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo ip6tables-save | sudo tee /etc/ip6tables.rules
If you use ufw:
sudo ufw allow 443/tcp
sudo ufw allow 80/tcp
ufw applies to both v4 and v6 by default. Verify:
sudo ip6tables -L INPUT -v -n | grep 443
In cloud provider control panels (AWS Security Groups, GCP firewall rules, DigitalOcean Cloud Firewalls), explicitly add IPv6 source ::/0 for ports 80/443 — many UIs default the source to 0.0.0.0/0 only.
Step 5: Add the correct AAAA record
After verifying the origin accepts v6, get the address:
ip -6 addr show | grep "scope global"
Take the non-temporary, non-link-local address (typically the one without temporary flag). Add it as an AAAA record in DNS with TTL 300 initially:
yourdomain.com. 300 IN AAAA 2001:db8::1
Test:
dig AAAA yourdomain.com @1.1.1.1 +short
curl -6 -I https://yourdomain.com
Step 6: If using a CDN, prefer the CDN’s IPv6 over manual AAAA
Cloudflare proxied (orange-cloud) records auto-publish both an A and an AAAA pointing at Cloudflare’s edge anycast addresses. Never manually add an AAAA that points past Cloudflare for a proxied record — IPv6 clients would bypass the edge entirely.
The on/off switch lives in Cloudflare dashboard → your domain → Network → IPv6 Compatibility (on by default; the toggle is only editable on Enterprise plans, otherwise it stays on). Leave it on unless you have a specific reason to drop v6. Note that for a proxied record with both an IPv4 and IPv6 origin, Cloudflare connects to your origin over IPv4 by preference, so your origin does not strictly need working v6 for edge-IPv6 to function.
For Vercel and Netlify, the platform manages the records for you when you use their nameservers or recommended CNAME/ALIAS target — do not hand-add an AAAA on top, because that creates a split where v6 clients reach a different endpoint than v4 clients. Check the current platform docs before adding any manual record.
Verify
dig AAAA yourdomain.com +shortreturns the correct address (or nothing, if intentionally v4-only).curl -6 -I https://yourdomain.comreturnsHTTP/2 200or expected response, not timeout.test-ipv6.com(from a network) shows site as v6-reachable.- An IPv6-only test from a mobile network (cellular data, hotspot to laptop) loads the site without delay.
ss -tlnpon the origin shows both v4 and v6 listeners on 443.
Long-term prevention
- Add IPv6 health checks to your monitoring (UptimeRobot, Pingdom, Better Stack all support v6 probes — must be explicitly enabled).
- When migrating hosts, update BOTH A and AAAA records as a single change; don’t leave stale AAAA pointing to an old VPS.
- Standardize nginx / haproxy / caddy templates to always include
[::]:443from day one. - Test from a mobile carrier monthly — most IPv6 issues only show up there.
- Document in your runbook: every public-facing service must have a working AAAA or no AAAA, never a broken one.
Common pitfalls
- Leaving a dead AAAA in place because “v4 still works” — you are silently degrading the experience of any user on a v6-preferring network.
- Adding AAAA but forgetting to open the v6 firewall — record points to a host that drops packets.
- Using
0.0.0.0/0everywhere and assuming it covers v6 too — most firewalls treat v4 and v6 as separate rule sets. - Setting AAAA TTL to 86400 then trying to fix a mistake — users keep the bad record for a day.
- Letting cloud provider auto-assign a temporary v6 (privacy address) and adding it to DNS — it changes on reboot.
FAQ
Q: Should I support IPv6 in 2026 even though my users are mostly desktop?
Yes. Mobile traffic is often majority IPv6 even when desktops are not. A broken AAAA actively harms users; no AAAA at all is acceptable, because Happy Eyeballs v2 (RFC 8305) falls back to IPv4 after a 250 ms Connection Attempt Delay when v6 does not connect.
Q: Can I just delete all AAAA records and forget about IPv6?
Yes, that is a valid stance. With no AAAA, every client uses IPv4 directly with no penalty. The bad failure mode is having an AAAA that does not work, not having none. If you are firefighting an outage, deleting the AAAA is the safest immediate move.
Q: How long until users stop hitting the dead IPv6 address after I delete the AAAA?
Up to the record’s TTL. Resolvers and browsers cache the old AAAA for that duration (commonly 4-24h). Lower the TTL to 300s before making the change so the cleanup propagates within five minutes; if you are mid-incident and the old TTL was high, there is no way to force-expire other people’s caches — you wait it out (or temporarily fix the v6 endpoint so the cached record works).
Q: My origin is on AWS — does it get IPv6 by default?
No. You must associate an IPv6 CIDR with the VPC, give the subnet a /64, and either set assign_ipv6_address_on_creation = true or attach an address to the instance/ENI. Legacy VPCs are IPv4-only until you migrate them. Note you cannot disable IPv4 on a standard subnet, but you can create IPv6-only subnets (--ipv6-native) if you want v6-first instances. Check VPC and subnet settings in the console.
Q: Does Cloudflare orange-cloud automatically give me IPv6?
Yes — proxied records publish both Cloudflare’s IPv4 and IPv6 anycast addresses regardless of your origin’s stack, controlled by the Network → IPv6 Compatibility toggle (on by default). This is the easiest way to support IPv6 without touching your origin, and Cloudflare still reaches your origin over IPv4.
See also A vs CNAME confusion, DNS changed site still down, and subdomain not resolving.
External references: the fallback timing comes from RFC 8305: Happy Eyeballs v2, and Cloudflare’s auto-AAAA behavior is documented under IPv6 compatibility. Confirm your own client first at test-ipv6.com.