How to check dns propagation

Nothing propagates. Each resolver holds its own copy until the TTL runs out, then fetches again. Ask several resolvers the same question and compare the answers. Agreement on the data with different TTLs is normal. Disagreement on the data is either a cache still counting down or a server that answers differently by design.

Checker offline. Follow the manual steps below, they give the same answer.

Why check this

Run this after every zone edit that a release depends on: a verification token a provider is waiting for, an A record moved to a new load balancer, an MX pointed at a new mail platform. The failure it prevents is the support ticket that says the change did not work, filed twenty minutes after an edit with a one-hour TTL, followed by a second edit that makes the state harder to reason about.

The word propagation describes a push that does not exist. Your provider publishes the change on its own nameservers at once. Every other resolver keeps serving its cached copy until that copy expires, and no two of them expire at the same moment. What you are checking is how many caches have refreshed yet.

Prerequisites

Steps

  1. Step 1.

    Ask one named resolver, rather than whatever the machine is configured with.

    nslookup -type=A example.com 1.1.1.1
    
    Non-authoritative answer:
    Server:  one.one.one.one
    Address:  1.1.1.1
    
    Name:    example.com
    Addresses:  104.20.23.154
      172.66.147.243

    Non-authoritative answer means this is a cached copy. Every recursive resolver labels its answers that way, including the correct ones.

  2. Step 2.

    Ask four resolvers the same question in one run. Save this as spread.js and run node spread.js example.com A.

    const { Resolver } = require('node:dns');
    const [name, type = 'A'] = process.argv.slice(2);
    const PUBLIC = [['8.8.8.8', 'Google'], ['1.1.1.1', 'Cloudflare'], ['9.9.9.9', 'Quad9'], ['208.67.222.222', 'OpenDNS']];
    const boot = new Resolver();
    boot.setServers(['8.8.8.8']);
    const ask = (ip, label) =>
      new Promise((d) => {
        const r = new Resolver({ timeout: 5000, tries: 2 });
        r.setServers([ip]);
        const done = (e, v) => {
          const text = e
            ? e.code
            : type === 'A'
              ? v.map((x) => `${x.address} ttl=${x.ttl}`).sort().join('  ')
              : JSON.stringify(v.flat().sort());
          console.log(label.padEnd(20) + ip.padEnd(17) + text.slice(0, 80));
          d();
        };
        if (type === 'A') r.resolve4(name, { ttl: true }, done);
        else r.resolve(name, type, done);
      });
    (async () => {
      const zone = name.split('.').slice(-2).join('.');
      for (const [ip, label] of PUBLIC) await ask(ip, 'cache ' + label);
      const ns = await new Promise((d) => boot.resolveNs(zone, (e, v) => d(e ? [] : v.sort())));
      for (const n of ns) {
        const ip = await new Promise((d) => boot.resolve4(n, (e, a) => d(e ? null : a[0])));
        if (ip) await ask(ip, 'authoritative');
      }
    })();
    
    cache Google        8.8.8.8          104.20.23.154 ttl=300  172.66.147.243 ttl=300
    cache Cloudflare    1.1.1.1          104.20.23.154 ttl=177  172.66.147.243 ttl=177
    cache Quad9         9.9.9.9          104.20.23.154 ttl=300  172.66.147.243 ttl=300
    cache OpenDNS       208.67.222.222   104.20.23.154 ttl=214  172.66.147.243 ttl=214
    authoritative       108.162.195.228  104.20.23.154 ttl=300  172.66.147.243 ttl=300
    authoritative       173.245.58.162   104.20.23.154 ttl=300  172.66.147.243 ttl=300

    Six servers, the same two addresses, four different TTL remainders in the caches and the configured 300 on both authoritative rows. The data has converged. The countdowns have not, and never will, because each cache started its own at a different moment.

  3. Step 3.

    Run the same comparison on the record type a provider is usually waiting for, and include the zone's own nameservers.

    node spread.js example.com TXT
    
    cache Google        8.8.8.8          ["_k2n1y4vw3qtb4skdx9e7dxt97qrmmq9","v=spf1 -all"]
    cache Cloudflare    1.1.1.1          ["_k2n1y4vw3qtb4skdx9e7dxt97qrmmq9","v=spf1 -all"]
    cache Quad9         9.9.9.9          ["_k2n1y4vw3qtb4skdx9e7dxt97qrmmq9","v=spf1 -all"]
    cache OpenDNS       208.67.222.222   ["_k2n1y4vw3qtb4skdx9e7dxt97qrmmq9","v=spf1 -all"]
    authoritative       172.64.35.228    ["_k2n1y4vw3qtb4skdx9e7dxt97qrmmq9","v=spf1 -all"]
    authoritative       108.162.192.162  ["_k2n1y4vw3qtb4skdx9e7dxt97qrmmq9","v=spf1 -all"]

    Six rows, one value. When the caches match the authoritative rows, the change is everywhere it is going to be. This is the state a verification button is waiting for.

  4. Step 4.

    Read a name where the resolvers disagree on the data, and run it twice.

    node spread.js aspmx.l.google.com A
    
    ### run 1
    cache Google        8.8.8.8          142.251.127.27 ttl=293
    cache Cloudflare    1.1.1.1          142.250.147.26 ttl=20
    cache Quad9         9.9.9.9          142.251.127.26 ttl=144
    cache OpenDNS       208.67.222.222   142.251.127.26 ttl=293
    authoritative       216.239.32.10    173.194.220.27 ttl=293
    authoritative       216.239.34.10    74.125.131.27 ttl=293
    authoritative       216.239.36.10    209.85.233.27 ttl=293
    authoritative       216.239.38.10    74.125.205.27 ttl=293
    
    ### run 2
    cache Google        8.8.8.8          142.251.127.27 ttl=293
    cache Cloudflare    1.1.1.1          142.251.9.26 ttl=173
    cache Quad9         9.9.9.9          142.251.127.26 ttl=144
    cache OpenDNS       208.67.222.222   142.251.127.26 ttl=293
    authoritative       216.239.32.10    64.233.163.27 ttl=293
    authoritative       216.239.34.10    64.233.162.26 ttl=293
    authoritative       216.239.36.10    142.250.150.27 ttl=293
    authoritative       216.239.38.10    64.233.165.27 ttl=293

    Eight servers and eight different addresses, twice, with nothing edited in between. The four authoritative rows disagree with each other and return a fresh set on the second run, so the zone is answering per query. Cloudflare's cached answer changed too, because its copy expired and it asked again. A difference like this never converges, and waiting for it to is a wasted afternoon.

How to read the result

| What you see | What it means | What to do | | --- | --- | --- | | Same data everywhere, different TTLs | Converged. The countdowns are supposed to differ | Nothing. The change is live | | Some resolvers on the old value, the rest on the new | Caches partway through the old TTL | Wait out the old TTL from the moment of the edit | | Every cache on the old value, the nameserver on the new | The edit is published and nothing has expired yet | Wait. A second edit restarts nothing and helps nothing | | The nameserver still returns the old value | The edit was not applied, or was applied to a different zone | Check the zone at the provider, and the delegation | | Answers differ every run, including from one resolver | The zone answers per client by design | Stop comparing. Verify the behaviour, not the address | | One resolver returns ENODATA and the rest return data | A negative answer is cached under the SOA minimum | Wait out the SOA minimum, which is often longer than the record TTL |

Common mistakes

Sign: Two resolvers return different addresses and the change is reported as still propagating.Cause: Step 4 shows eight servers returning eight addresses for a name nobody edited, twice over. Some zones answer per query. Before you call a difference propagation, read the authoritative servers: if they disagree with each other, the zone is doing it on purpose and no amount of waiting changes it.
Sign: The record is edited again because a resolver still shows the old value.Cause: A second edit does not reach a resolver that is not asking. The cached copy is held for the remainder of its TTL whatever the zone now says. Editing again only makes the eventual state harder to predict, because different caches will refresh into different versions.
Sign: A TXT verification passes on a checking website and the provider's own button still fails.Cause: A propagation website queries from its own locations with its own caches. The provider queries from theirs. Neither observation covers the other. The only reading that settles it is the one in step 3: every cache you can reach agrees with the zone's own nameservers.

What to check next

FAQ

How to check if dns has propagated?

Query the zone's authoritative nameservers and several public resolvers for the same name and type, as in step 3. When every cache returns what the nameservers return, there is nothing left to wait for. Differing TTLs between caches are expected and mean nothing.

Nslookup with specific dns server, what is the syntax?

The resolver goes last: nslookup -type=TXT example.com 9.9.9.9. Without it, nslookup uses the resolver the operating system supplies, which during a change is the least informative one you can ask.

How to check if txt record has propagated?

Run step 3 with the exact name the provider gave you, underscore label included. Read the strings as a set. A record printed as several quoted strings is one record, and the value is those strings joined end to end.

How long does a DNS change take?

As long as the old TTL, counted from the moment a resolver last fetched the record, which can be up to the old TTL before your edit. Plan for the old TTL plus your provider's publish delay, and lower the TTL one full old TTL in advance.

Why do two checking websites disagree?

They query from different places through different caches. Both can be right at the same second. Compare the caches you care about against the authoritative servers instead of comparing two third parties against each other.

Verified

Verified by Maks Vernynslookup Windows 11 build 22631node 22.23.2

Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.

intermediate8 minpublished updated Maks Verny