How to check txt record of a domain

Run nslookup -type=TXT github.com 8.8.8.8 and read the quoted strings. One record can be printed as several quoted strings on separate lines, and the value is those strings joined with nothing between them. Node's resolveTxt returns each record as an array, which makes the split visible.

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

Why check this

Run this whenever a third party says "add this record and click verify", and again after any zone edit near it. Domain verification for a search console, a workspace, a payment provider or a certificate authority all live in TXT records, and so do the policies read by every receiving mail server. The failure it prevents is a verification that stays pending for a day because the token was added at example.com rather than at the underscore label the provider named.

The second failure is quieter. A record longer than one DNS string is stored as several strings, and reading only the first one gives a value that is well formed and wrong. Step 2 shows a record where the split falls in the middle of an IPv4 address.

Prerequisites

Steps

  1. Step 1.

    List the TXT records at the name.

    nslookup -type=TXT github.com 8.8.8.8
    
    Non-authoritative answer:
    Server:  dns.google
    Address:  8.8.8.8
    
    github.com	text =
    
    "MS=ms44452932"
    github.com	text =
    …
    "openai-domain-verification=dv-3nh33eQwdkotMIAzLrIDVZo1"
    github.com	text =
    
    "v=spf1 ip4:192.30.252.0/22 include:spf.protection.outlook.com include:_netblocks.google.com include:_netblocks2.google.com include:mail.zendesk.com include:_spf.salesforce.com include:servers.mcsv.net include:mktomail.com include:sendgrid.net ip4:62.253.2"
    "27.114 ip4:166.78.69.169 ip4:166.78.69.170 ip4:166.78.71.131 ~all"
    github.com	text =

    One record, two quoted strings, two lines. Every other record here is one string on one line.

  2. Step 2.

    Read the same records as arrays, so the boundary between strings is explicit. Save it as txt-chunks.js and run node txt-chunks.js github.com.

    const { Resolver } = require('node:dns');
    const name = process.argv[2];
    const r = new Resolver();
    r.setServers(['8.8.8.8']);
    r.resolveTxt(name, (err, records) => {
      if (err) { console.log(err.code); return; }
      for (const rec of records) {
        console.log(rec.length + ' string(s), ' + rec.join('').length + ' bytes joined');
        rec.forEach((s, i) => console.log('  [' + i + '] ' + s));
      }
    });
    
    1 string(s), 55 bytes joined
    [0] loom-site-verification=f3787154f1154b7880e720a511ea664d
    1 string(s), 62 bytes joined
    [0] anthropic-domain-verification-4az7qn=if8YWuRRqwLycGJDooumzHtxm
    …
    2 string(s), 320 bytes joined
    [0] v=spf1 ip4:192.30.252.0/22 include:spf.protection.outlook.com include:_netblocks.google.com include:_netblocks2.google.com include:mail.zendesk.com include:_spf.salesforce.com include:servers.mcsv.net include:mktomail.com include:sendgrid.net ip4:62.253.2
    [1] 27.114 ip4:166.78.69.169 ip4:166.78.69.170 ip4:166.78.71.131 ~all
    1 string(s), 56 bytes joined
    …

    String [0] is 255 bytes and ends inside an address. Joined with nothing, the two give ip4:62.253.227.114. Joined with a space, or read alone, they give ip4:62.253.2, which is a different address and a syntactically valid one.

  3. Step 3.

    Query two invented names and a real one, and compare the errors. Save it as txt-exists.js and run it with the names as arguments.

    const { Resolver } = require('node:dns');
    const r = new Resolver();
    r.setServers(['8.8.8.8']);
    for (const n of process.argv.slice(2)) {
      r.resolveTxt(n, (err, recs) => console.log(n.padEnd(30) + (err ? err.code : recs.length + ' record(s)')));
    }
    
    h2check-probe.mozilla.org     ENOTFOUND
    example.com                   2 record(s)
    h2check-probe.cloudflare.com  ENODATA

    Both invented names are absent, and they came back different. The mozilla.org zone answered that the name does not exist, ENOTFOUND. The cloudflare.com zone answered with an empty record set, ENODATA, which is also what a real name with no TXT record gets. So ENODATA does not prove the name exists.

  4. Step 4.

    Run the step 3 script at an aliased name and at its target.

    node txt-exists.js www.github.com github.com
    
    www.github.com                24 record(s)
    github.com                    24 record(s)

    The alias holds no TXT records of its own. The resolver followed the CNAME and answered with the target's 24.

How to read the result

| What you see | What it means | What to do | | --- | --- | --- | | One quoted string per line, one record per text = | Ordinary records, each value complete | Compare the value with what the provider issued, character for character | | Two quoted strings under one text = | One record stored as several strings | Join them with nothing between, then compare | | ENODATA | No TXT record in the answer. Not proof the name exists: step 3's invented name gave it too | Check the label. A token at _acme-challenge is not at the registrable domain | | ENOTFOUND | The name itself does not exist | Check the spelling and whether the label was created at all | | The same records at a name and at its parent | The name is a CNAME and the answer is the parent's | Read How to check cname record. A TXT cannot be added beside a CNAME |

Thresholds

255 bytes, the maximum length of one character-string inside a TXT record Source: RFC 1035 section 3.3.14, confirmed by the 255-byte first string measured in step 2

Common mistakes

Sign: A long record is copied out of nslookup and the value is shorter than the one the provider published.Cause: nslookup prints each string of a multi-string record on its own line, and a copy that stops at the first closing quote loses the rest. The github.com record in step 1 splits at ip4:62.253.2, so the truncated copy still parses and still names a real address.
Sign: Two strings are joined with a space or a newline and the parsed value is subtly wrong.Cause: RFC 7208 section 3.3 concatenates the strings with nothing between them. The split in step 2 falls inside 62.253.227.114, so any separator produces a different address rather than an obvious syntax error.
Sign: A verification token was added, the lookup returns records, and the provider still reports the domain unverified.Cause: The token belongs at the label the provider named, often one beginning with an underscore, and a query at the registrable domain answers from a different node of the tree. ENODATA at the right label and records at the wrong one look alike to a tester who only checks one of them.
Sign: A TXT record is added at a www name and never takes effect.Cause: That name is an alias. Step 4 shows the resolver answering a TXT query at www.github.com with github.com's records, because a CNAME may not share a name with any other record. The new TXT is unreachable whatever the zone editor shows.

What to check next

FAQ

How to check if a txt record exists?

Query the exact name. Records in the answer settle it. ENOTFOUND means the name itself is absent. ENODATA means no TXT record came back, and step 3 shows it for an invented name as well, so it does not prove the label was created.

How to check txt record nslookup?

nslookup -type=TXT <name> <resolver>. Naming the resolver matters when a change is recent, because a cached answer from the resolver your machine defaults to can be older than the zone.

How to verify dns txt record after adding it?

Ask the authoritative nameserver from the SOA answer as well as a public resolver. The authoritative server shows the zone as it stands now, a public resolver shows what clients still hold. A difference between them is the TTL running down, not a mistake.

Why is one record printed as two quoted strings?

Because a single string in a TXT record is capped at 255 bytes, so anything longer is stored as several. The count is a storage detail and the value is the strings joined end to end.

Does the order of TXT records matter?

No. A name can hold many TXT records and the order in an answer is not stable. Each consumer looks for its own prefix, which is why tokens carry one.

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.

basic5 minpublished updated Maks Verny