web-cp.net

"open source web hosting control panel"

← Back to Blog
Why DNS Looks Correct but Visitors Still Reach the Old Server

Why DNS Looks Correct but Visitors Still Reach the Old Server

2026-10-19 · Ethan Caldwell Manuals

A site is moving to a new server. You have updated the A record in the control panel, an online DNS checker shows the new address, and dig on your laptop agrees. But a client calls to say their orders are still arriving on the old server. A monitoring probe in another country keeps hitting the previous IP. Mail is landing in the old mailboxes. And the new server’s access log shows only part of the traffic you expected.

The usual explanation is “DNS propagation, just wait”. It is vague, and it is often wrong. DNS doesn’t propagate like a wave across the internet. Answers are copied into many independent caches, each of which keeps its copy for a set time and then asks again. Several of the most common causes of “still on the old server” aren’t DNS caching at all: forgotten records, delegation that was never changed, a secondary nameserver serving an old zone, long-lived connections, or software that caches resolution on its own terms.

This guide follows the path a visitor’s request takes to your server, identifies each place where an old answer can survive, and shows how to tell which one you’re dealing with.

The chain a lookup actually travels

When a browser needs the address of shop.example, a series of different systems are involved. Each can hold a copy of an old answer.

  1. The application may keep its own cache or reuse an existing connection without resolving again.

  2. The operating system may cache answers locally. On many Linux distributions this is systemd-resolved; on macOS it is mDNSResponder; on Windows it is the DNS Client service.

  3. The recursive resolver answers on the client’s behalf. It is usually run by the ISP, a company network or a public service, and its cache is shared by many users.

  4. The parent zone (for .example, the TLD servers) tells resolvers which nameservers are authoritative for your domain. This is delegation, and the registrar controls it.

  5. The authoritative nameservers hold your zone and give the definitive answer. They may be your control panel’s own DNS server, a secondary DNS provider or a third-party DNS host.

The registrar and the DNS host are often different organisations. The registrar records which nameservers are authoritative for the domain and sends that information to the registry, which publishes it in the parent zone. The DNS host runs those nameservers and serves the zone’s records. Editing records in a control panel changes nothing if the domain is delegated to a different set of nameservers.

html

<table>
  <thead>
    <tr>
      <th>Layer</th>
      <th>What it stores</th>
      <th>How long an old answer can survive</th>
      <th>Who controls it</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Authoritative nameserver</td>
      <td>The zone itself, with records and their TTLs</td>
      <td>Until the zone is edited and reloaded on every authoritative server</td>
      <td>You or your DNS host</td>
    </tr>
    <tr>
      <td>Parent zone (TLD)</td>
      <td>NS delegation and, where needed, glue addresses</td>
      <td>Until the registrar updates the registry, then the delegation TTL in resolver caches</td>
      <td>Registrar and registry</td>
    </tr>
    <tr>
      <td>Recursive resolver</td>
      <td>Cached answers, including negative answers</td>
      <td>Up to the record's TTL, sometimes capped or extended by resolver policy</td>
      <td>ISP, company IT or public resolver operator</td>
    </tr>
    <tr>
      <td>Operating system cache</td>
      <td>Recent lookups for local applications</td>
      <td>Normally up to the TTL received from the resolver</td>
      <td>The client device</td>
    </tr>
    <tr>
      <td>Application</td>
      <td>Its own cache, or open connections that bypass resolution</td>
      <td>Depends entirely on the software</td>
      <td>The application or runtime</td>
    </tr>
  </tbody>
</table>

TTL is a promise about the past, not the future

Every DNS record carries a TTL (time to live), in seconds. When a recursive resolver receives an answer, it may cache it for that long. When it answers its clients, it hands out the remaining time, which counts down in its cache until it reaches zero. At that point the resolver asks the authoritative servers again.

The important consequence is that the TTL in force when a resolver last fetched the record is what matters, not the TTL you set now. If an A record had a TTL of 86400, one day, and you change both the address and the TTL at noon, a resolver that fetched the record at 11:59 can keep the old address until 11:59 the next day. Your new 300-second TTL only takes effect once that resolver asks again.

This is why the standard migration advice is “lower the TTL in advance”, and also why it often fails in practice. Lowering the TTL only helps if you wait at least as long as the old TTL before making the actual change. The sequence that works looks like this:

html

<table>
  <thead>
    <tr>
      <th>Step</th>
      <th>Action</th>
      <th>Why the timing matters</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>1</td>
      <td>Note the current TTL of every record that will change (A, AAAA, CNAME targets, MX hosts)</td>
      <td>This is how long the slowest cache may keep the old answer</td>
    </tr>
    <tr>
      <td>2</td>
      <td>Lower those TTLs, for example to 300 seconds, without changing any address</td>
      <td>The old long TTL still applies to copies already cached</td>
    </tr>
    <tr>
      <td>3</td>
      <td>Wait at least one full old-TTL period, plus a margin</td>
      <td>After this, every cache that still holds the record received the short TTL</td>
    </tr>
    <tr>
      <td>4</td>
      <td>Change the addresses</td>
      <td>Caches now expire the old answer within about 300 seconds</td>
    </tr>
    <tr>
      <td>5</td>
      <td>Once traffic has moved and the old server is quiet, raise the TTLs again</td>
      <td>Long TTLs reduce query load and protect against short outages at the DNS host</td>
    </tr>
  </tbody>
</table>

If the record had a TTL of a day and you lowered it an hour before the change, you saved almost nothing.

Resolver policies, caps and stale answers

Most recursive resolvers respect the TTL, but their own policies modify it at the edges.

Many resolvers cap the maximum TTL they will cache. In Unbound, for example, cache-max-ttl defaults to 86400 seconds, one day. A record published with a week-long TTL is therefore not cached for a week by an Unbound resolver with default settings. Some resolvers also apply a minimum TTL, which can keep a very short TTL alive longer than you asked. In Unbound, cache-min-ttl defaults to 0, meaning the minimum is disabled, but operators can raise it.

Resolvers may also serve stale data. RFC 8767 describes how a resolver can keep returning an expired record when it can’t reach the authoritative servers. That keeps sites reachable during DNS outages. The RFC suggests keeping stale records for between one and three days, and serving them with a TTL of 30 seconds. In Unbound this behaviour is controlled by serve-expired, which is off by default, but large public resolvers may enable it.

Serving stale data normally only happens when the authoritative servers don’t answer. It becomes relevant to a migration when the old DNS provider is shut down or the old nameservers are switched off before delegation has moved. A resolver that can’t reach any authoritative server may then keep serving the last known answer, which points at the old server.

Negative caching, the record that didn’t exist

A frequent and confusing case involves records that were looked up before they existed. Suppose you test new.shop.example before creating it, or a monitoring check queries a hostname that you add later. The resolver receives an NXDOMAIN or “no data” answer and caches that absence as well.

RFC 2308 defines how long: the negative answer is cached for the smaller of two values, the SOA record’s MINIMUM field and the TTL of the SOA record itself. If your zone’s SOA has a MINIMUM of 3600, a resolver that asked for the record a moment before you created it may report that it doesn’t exist for up to an hour, even though the authoritative servers already return it. Unbound limits this with cache-max-negative-ttl, which defaults to 3600 seconds.

The practical rule is to create new records before anyone queries them, not after. When a fresh hostname “doesn’t resolve” for some users and works for others, check the SOA timers before blaming the registrar.

When the change was made in the wrong place

Some of the longest “propagation” incidents involve no caching at all. The change was simply made somewhere that doesn’t answer for the domain.

Editing a zone that isn’t delegated. A control panel can host a complete zone for shop.example that nobody queries, because the registrar still delegates the domain to another provider’s nameservers. The panel shows the new address and its own nameserver answers correctly when asked directly, but resolvers on the internet never ask it. This is common after migrations, when the new panel creates a zone automatically and the administrator assumes it is live.

Changing delegation and waiting on the parent. When you switch nameservers at the registrar, the change goes to the registry, which publishes new NS records in the parent zone. Resolvers that cached the old delegation keep using it until its TTL expires. For many TLDs these delegation TTLs are long: the .com zone publishes NS records with a TTL of 172800 seconds, two days. A resolver with the old delegation cached can therefore keep asking the old nameservers for up to two days, or less if its own cap is lower. If the old nameservers still answer with the old zone, those resolvers get the old address. This is why the old DNS host should keep serving a correct, updated zone until the delegation change has fully expired.

Glue that still points to the old addresses. If the nameservers live inside the domain they serve, for example ns1.shop.example for shop.example, the parent zone must also publish their addresses as glue records. Otherwise resolvers can’t reach them. When such nameservers change IP address, the glue must be updated at the registrar, usually as host objects. Editing the A record for ns1.shop.example inside your own zone isn’t enough, because resolvers use the glue address to reach the nameserver in the first place.

A secondary nameserver serving an old zone. In setups with a primary and one or more secondary nameservers, secondaries refresh their copy of the zone through zone transfers, triggered by NOTIFY messages and checked against the SOA serial number. If the serial number wasn’t increased when the zone was edited, secondaries may consider their copy current and keep serving the old records. Resolvers spread their queries across all authoritative servers, so some users get the new answer and others get the old one, apparently at random. Many panels manage the serial automatically, but manual edits to zone files, imports and custom scripts can bypass that mechanism.

The records people forget

Even when the main A record is correct, part of the traffic can follow other records that still point to the old infrastructure.

AAAA records. If the old server had an IPv6 address published in an AAAA record and the new zone keeps it, or nobody updates it, clients with working IPv6 will often prefer it. Under Happy Eyeballs, a client that has both addresses races IPv6 and IPv4 and usually favours IPv6. A significant share of users can therefore still reach the old server while every IPv4 test you run shows the new address. Every migration checklist should include AAAA records explicitly.

CNAME chains. A record such as www.shop.example may be a CNAME to another name, possibly in a different zone with its own TTL. Updating shop.example doesn’t change where www resolves if the CNAME target is elsewhere.

MX records and mail hostnames. Mail delivery follows the MX record to a hostname, and that hostname to its own A and AAAA records. Updating the website’s A record doesn’t move mail if the MX points to mail.shop.example, which still has the old address. Mail clients configured with an explicit IMAP or SMTP hostname behave the same way. Sending servers also cache MX lookups and retry queued messages for days, so messages may keep arriving at the old server well after the change.

Wildcards and more specific records. A wildcard record such as *.shop.example doesn’t apply to names that have their own explicit records. A forgotten explicit record for a single subdomain overrides the updated wildcard.

Caches outside DNS, applications and connections

Once DNS is correct everywhere, traffic can still reach the old server through mechanisms that never ask DNS again.

Persistent connections. HTTP keep-alive, HTTP/2 and HTTP/3 connections stay open and carry many requests. A browser, API client or reverse proxy that opened a connection to the old server keeps using it until the connection closes, regardless of DNS. Long-running clients such as payment integrations, mobile apps or websocket connections can stay attached to the old server for a long time.

Application-level caches. Some runtimes and applications cache DNS answers on their own terms. Java is the classic example: the networkaddress.cache.ttl security property controls how long successful lookups are cached. Current JDKs default to 30 seconds when no security manager is installed, but applications may override it, and older Java deployments were known to cache for much longer. Reverse proxies and load balancers often resolve upstream hostnames only at startup or reload unless configured to re-resolve. An NGINX proxy_pass with a fixed hostname, for example, is resolved when the configuration is loaded unless a resolver is configured and the hostname is passed through a variable.

CDNs and external proxies. If the site sits behind a CDN, visitors never resolve the origin’s address. Their DNS points to the CDN, and the origin address is configured in the CDN account. Changing the A record in the panel then has no effect on visitors. You need to update the origin setting in the CDN, and possibly purge its cache, because cached pages served by the CDN may also come from the old origin.

Hosts files and local overrides. The hosts file that was edited to test the new server before the switch is a common culprit in reverse. Machines that still carry an entry pointing at the old address bypass DNS entirely. The same applies to split-horizon DNS in office networks, where the internal DNS server answers with its own records.

TLS during the switch

A migration also affects certificates. When visitors start reaching the new server, it must present a valid certificate for the hostname. If it doesn’t have one yet, browsers show a certificate error. Visitors may then report “the site is broken”, which looks like a DNS problem but isn’t.

Certificate issuance with ACME, the protocol used by Let’s Encrypt and other automated CAs, depends on DNS too. With HTTP-01 validation, the CA connects to the hostname as resolved through DNS. If DNS still points to the old server, the challenge is served by the old server and validation fails on the new one. There are three safe ways around this:

  • Copy the current certificate and private key from the old server to the new one before the switch. Transfer them over a secure channel, and set ownership and permissions so that only the web server’s service account can read the key.

  • Use DNS-01 validation, which proves control through a TXT record rather than through the web server, and can be completed before the address changes.

  • Issue the certificate right after the switch, and accept a short window of errors for users who already reach the new server. This is the least comfortable option.

You can check which certificate the new server presents before changing DNS by connecting to it directly while sending the right hostname:

bash

openssl s_client -connect 203.0.113.10:443 -servername shop.example </dev/null | openssl x509 -noout -subject -issuer -dates

The -servername option sends the hostname in the TLS handshake (SNI), so the server selects the certificate it would present to real visitors. For HTTP-level testing, curl’s --resolve option does the same job for a full request: it maps a hostname and port to a specific address for that single command, without touching the hosts file.

DNSSEC changes when the DNS host changes

Domains signed with DNSSEC add one more trap. Validating resolvers check signatures against the DS record published in the parent zone. That record identifies the key used by the current DNS host. If you move the zone to a new DNS host that signs with different keys while the parent still publishes the old DS record, validating resolvers can’t verify the new answers and return SERVFAIL. Users behind those resolvers get no answer at all, while users behind non-validating resolvers see the site normally.

A DNS host change on a signed domain needs a planned key transition: either a coordinated key rollover between the two providers, or removing the DS record and waiting for its TTL to expire before the move, then signing again once the new provider is live. It isn’t a step to improvise during a migration.

Diagnosing which layer holds the old answer

The way to stop guessing is to ask each layer directly and compare the answers. The commands below use dig, which on Debian and Ubuntu is in the dnsutils package (or bind9-dnsutils on newer releases), and on RHEL, AlmaLinux, Rocky Linux and Fedora is in bind-utils.

Ask the authoritative servers themselves. Find the nameservers delegated for the domain, then query each one with recursion disabled:

bash

dig +short NS shop.example
dig +norecurse @ns1.newhost.example shop.example A
dig +norecurse @ns2.newhost.example shop.example A
dig +norecurse @ns1.newhost.example shop.example AAAA

Each answer shows the configured TTL and address. If two authoritative servers disagree, the problem is in zone distribution, such as a secondary with an old serial, not in caching. Compare the SOA serial on each server:

bash

dig +norecurse @ns1.newhost.example shop.example SOA +short
dig +norecurse @ns2.newhost.example shop.example SOA +short

Check what the parent zone delegates. A trace follows the delegation from the root down and shows which nameservers the TLD actually lists, with their TTL:

bash

dig +trace shop.example A

If the trace ends at the old provider’s nameservers, the registrar change hasn’t been made or hasn’t yet reached the registry. No amount of editing in the new panel will help until it has.

Ask several recursive resolvers. Querying public resolvers shows what different caches currently hold and how much TTL remains:

bash

dig @1.1.1.1 shop.example A
dig @8.8.8.8 shop.example A
dig @9.9.9.9 shop.example A

The TTL in each response is the time left before that cache asks again. An old address with a remaining TTL of 70000 seconds tells you exactly how long that resolver will keep it. Both Cloudflare and Google provide web tools that let you request a cache purge for a specific name on their public resolvers. That helps with those resolvers but has no effect on the caches of other ISPs.

Check the local machine. On systems using systemd-resolved, resolvectl query shop.example shows what the local stub resolver returns, and resolvectl flush-caches clears its cache. On macOS, sudo dscacheutil -flushcache followed by sudo killall -HUP mDNSResponder clears the system cache. On Windows, ipconfig /flushdns does the same. These commands only fix the machine you run them on, so they are for testing, not a remedy for visitors.

Look at the old server’s logs. The most reliable evidence is the traffic itself. If the old server still receives requests, its access log shows which clients are still arriving. Group them by user agent and source network. Many requests from one hosting provider’s range suggest an integration with its own DNS caching. Mostly IPv6 sources point to an AAAA record. Steady traffic over the same long-lived connections points to keep-alive rather than DNS.

html

<table>
  <thead>
    <tr>
      <th>Symptom</th>
      <th>Most likely cause</th>
      <th>Check</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Authoritative servers disagree with each other</td>
      <td>Secondary not updated, SOA serial not increased</td>
      <td>Compare SOA serials with queries to each nameserver</td>
    </tr>
    <tr>
      <td>The panel shows the new record, but the trace ends at other nameservers</td>
      <td>Domain delegated elsewhere, registrar not updated</td>
      <td>Run dig +trace and compare the NS records with the panel's nameservers</td>
    </tr>
    <tr>
      <td>Public resolvers return the old address with a high remaining TTL</td>
      <td>TTL lowered too late, or not lowered</td>
      <td>Compare the remaining TTL with the time of the change</td>
    </tr>
    <tr>
      <td>A new hostname fails for some users for up to an hour</td>
      <td>Negative caching from earlier queries</td>
      <td>Check the SOA MINIMUM and SOA TTL values</td>
    </tr>
    <tr>
      <td>IPv4 tests show the new server, but part of the traffic still reaches the old one</td>
      <td>Stale AAAA record</td>
      <td>Query the AAAA record and check the old server's logs for IPv6 sources</td>
    </tr>
    <tr>
      <td>Validating resolvers return SERVFAIL after a DNS host change</td>
      <td>DS record no longer matches the signing keys</td>
      <td>Compare the DS record in the parent with the DNSKEY records of the new provider</td>
    </tr>
    <tr>
      <td>Requests keep arriving on the old server long after every DNS check is clean</td>
      <td>Persistent connections, application caches, hosts files or a CDN origin setting</td>
      <td>Inspect the clients in the old server's logs and the CDN origin configuration</td>
    </tr>
  </tbody>
</table>

Designing a migration that doesn’t depend on luck

Every one of these mechanisms works as designed. The problems come from cutovers that assume a single cache and an instant change. A migration that accounts for each layer looks like this:

  1. Inventory the records first. List every record that will change, including AAAA, MX hosts, CNAME targets and any explicit subdomains, with their current TTLs.

  2. Lower the TTLs, then wait one full old-TTL period. Only then change the addresses.

  3. Decide where DNS will live. If the DNS host is also changing, recreate the full zone at the new provider, verify it with direct queries, and only then change the delegation at the registrar. Keep the old provider serving an up-to-date zone for at least the delegation TTL. On signed domains, plan the DNSSEC transition separately.

  4. Prepare the new server’s certificates before any traffic arrives.

  5. Make the old server harmless rather than switching it off. Requests that still reach it can be proxied to the new server or answered with a clear maintenance message. Writes, such as orders, form submissions and mail, should not be accepted silently on a server that is about to disappear. Where possible, put databases into read-only mode on the old server, or forward application traffic to the new one.

  6. Watch the old server’s logs until they go quiet, then raise the TTLs again and decommission it.

Following these steps doesn’t make DNS faster. It makes the timeline predictable. The old answers expire in a known order, each remaining source of old traffic can be identified, and none of them causes data to be lost on a server that nobody is watching.