Bali, Indonesia
Insights

Let's Encrypt Fails on a Stale AAAA Record Even When the A Record Is Right

October 27, 2026
0:00
A certificate renewal fails. You check the A record and it points at the right server. You open the challenge path in a browser and the site loads fine. The renewal still fails with a 404 or a connection error. In many of these cases the A record was never the problem. The problem is an AAAA record, the IPv6 version of an A record, that still points at a server you stopped using a while ago. This catches people because most of us think of a domain as having one address. It can have two independent ones, an IPv4 address in the A record and an IPv6 address in the AAAA record, and nothing in DNS forces them to point at the same machine. Update one during a migration and forget the other, and you now have a domain that quietly lives in two places.
With the HTTP-01 challenge, your ACME client (Certbot, acme.sh, a control panel's AutoSSL, and so on) places a token file under /.well-known/acme-challenge/ on your web server. Let's Encrypt then has to fetch that file from the outside to prove you control the domain. The validation service does not use your browser's logic or your laptop's resolver. It runs its own DNS lookups and asks for both record types. When it gets back an A record and a AAAA record, Let's Encrypt typically prefers the IPv6 address and opens the connection there first. The same applies to the TLS-ALPN-01 challenge, which connects on port 443 instead of port 80. DNS-01 is different: it only reads a TXT record, so a stale AAAA has no effect on it. So the sequence looks like this:
  1. The validator resolves example.com and gets both an IPv4 and an IPv6 address.
  2. It picks the IPv6 address and sends a TCP SYN (the first packet of a connection) to port 80 on that address.
  3. Whatever machine owns that address decides what happens next.
  4. If the connection succeeds, the validator sends GET /.well-known/acme-challenge/<token> with your domain in the Host header and checks the response body against the expected token.
If the AAAA record is correct, this all works and you never think about it. If it is stale, step 3 is where things go wrong.
What you see in the error depends on what is sitting at the old address, and each case looks different at the packet level. The old server is still running. This is the most confusing case. The SYN reaches a real machine with a web server on port 80, the TCP handshake completes, and the HTTP request goes through. But the token file was written on your new server, not this one. The old server answers with a 404, or it serves some default page, or it serves an outdated copy of your site. The validator gets a valid HTTP response with the wrong content and marks the challenge as failed. Because the connection itself worked, there is usually no reason for it to try IPv4 instead. The address is no longer in use. Maybe the old VPS was destroyed and the IPv6 address was never reassigned. The SYN goes out and nothing replies. There is no handshake and no rejection, just silence until the validator gives up, which shows up as a timeout. The address belongs to a host that is up but not listening. Here the remote kernel receives the SYN, finds nothing bound to port 80, and replies with a TCP RST. The validator sees connection refused almost immediately. For the last two cases, Let's Encrypt typically falls back to IPv4 after a connection-level failure, so a dead IPv6 address does not always break renewal on its own. The first case is the one that reliably fails, because from the validator's point of view the server answered and simply gave the wrong answer. It is also worth knowing that Let's Encrypt validates from more than one network location, so an IPv6 address that is only partly reachable can produce results that seem to change from attempt to attempt. Redirects make this a little harder to spot. HTTP-01 follows redirects, and every redirect target is resolved again. If example.com is fine but it redirects to www.example.com, and www has the stale AAAA, the failure happens on the second hop.
This is usually what confuses site owners when they contact support. They test the challenge URL themselves, it loads, and they reasonably conclude the server is fine. There are a few reasons their test does not match what the validator sees. Many office and home networks have no working IPv6 at all, so the browser only ever uses the A record. Networks that do have IPv6 often use Happy Eyeballs, a technique where the browser tries IPv6 and IPv4 in parallel and goes with whichever connects first, which can hide a broken IPv6 path. And testing with curl from the new server itself usually goes over IPv4 or loopback, which proves nothing about the AAAA record. The most useful clue is in the error message. Most ACME clients print the IP address the validator connected to. If that address contains colons, the validator used IPv6, and you should compare it with what your server actually has. The exact wording depends on the client or the control panel, but the address is usually there.
Start by asking what DNS is actually publishing, and ask the authoritative nameservers directly so you are not looking at a cached answer. Check every name on the certificate, not only the bare domain, and include any host that a redirect points to.
BASH
# Which nameservers are authoritative for the zone
dig +short NS example.com

# Ask one of them directly for both record types
dig +short A example.com @ns1.example-dns.net
dig +short AAAA example.com @ns1.example-dns.net

# Repeat for every name on the certificate
dig +short AAAA www.example.com @ns1.example-dns.net
Replace ns1.example-dns.net with one of the nameservers returned by the first command. If you get an IPv6 address back, compare it with the addresses actually configured on the server that should be answering:
BASH
# IPv6 addresses configured on this server (global scope only)
ip -6 addr show scope global

# Test the challenge path over IPv6 only, from a machine with working IPv6
curl -6 -sI 'http://example.com/.well-known/acme-challenge/test'
If the AAAA address does not appear in the ip output, the record points somewhere else. The curl -6 test needs to run from a machine that has working IPv6 connectivity, otherwise it fails for reasons unrelated to your DNS. A 404 on a made-up filename is expected, so focus on which server responds. Response headers such as Server or a different set of cookies often show that you reached a different machine than you expected.
You have two reasonable fixes. If the new server has IPv6 and you want to use it, update the AAAA record to the correct address and confirm the web server is actually listening on IPv6. Whether Apache or Nginx binds IPv6 depends on the existing configuration and distro packaging. In Nginx, for example, an IPv6 listener is usually set with a separate listen [::]:80; line in the server block. If the new server has no IPv6, or you are not ready to support it, delete the AAAA record. A missing AAAA is harmless. A wrong one is not. After the change, give caches time to expire. Resolvers, including the ones the CA uses, may keep the old answer until the record's TTL runs out, so retrying a renewal one minute after editing the zone can fail for reasons that are already fixed. Check the authoritative answer again with dig, wait out the TTL, then retry. The longer-term fix is making AAAA records part of routine DNS maintenance rather than something you only look at when a renewal breaks. A few habits help. When a server is migrated or retired, review every AAAA record that pointed at it, not just the A records. When importing a zone from an old provider or a backup, treat imported AAAA records as suspect until you confirm the address belongs to a live host you control. On a regular cycle, compare each published AAAA against the addresses on the server it should point to. This is a short check, and it fits naturally next to the other DNS checks that affect issuance, such as CAA records, which are covered in Fixing CAA Failures When Switching from Manual SSL to ACME Clients. When a renewal fails and the A record looks right, read the IP address in the error before anything else. If it is an IPv6 address, run dig AAAA against the authoritative nameservers for every name on the certificate and compare the result with ip -6 addr on the real server. Most of the time the fix is either correcting one record or deleting it, and the harder part is remembering that the domain had a second address in the first place.