
How to verify domain ownership with a TXT record?
To verify domain ownership with a TXT record, copy the verification value the service gives you (for example google-site-verification=abc123...), log in to your domain's DNS provider, and create a new TXT record with the host name the service specifies, usually @ for the root domain or a special name such as _acme-challenge. Save it, confirm it's visible with dig +short example.com TXT, then click Verify in the service. Because only someone who controls your DNS can publish that exact value, the service accepts it as proof that you own the domain.
TXT verification is used by search consoles, email platforms, cloud providers, certificate authorities, social networks and SaaS tools of every kind. The process is the same everywhere, but small details such as the host field, quotation marks and existing records cause most failures. This guide explains how TXT verification works, how to add the record correctly, how to check it from the command line and in scripts, why verification sometimes fails, and what to do with old verification records.
How TXT Verification Works
A TXT record holds arbitrary text attached to a DNS name. Verification uses this to set a simple challenge:
- The service generates a random token that's unique to your account.
- You publish the token in a TXT record on your domain.
- The service looks up the TXT records for your domain, using public DNS.
- If it finds the exact token, it knows that whoever controls the domain's DNS also controls the account.
This works because publishing DNS records for a domain requires access to its DNS provider, which only the domain's owner (or someone they've authorised) should have. The token itself isn't secret. Anyone can look it up once it's published, but that doesn't help them, because the token is tied to your account at the service, not theirs.
Common Verification Formats
Each service uses its own prefix so tokens don't collide. Some typical examples:
| Service | Host | Value format |
|---|---|---|
| Google (Search Console, Workspace) | @ | google-site-verification=<token> |
| Microsoft 365 | @ | MS=ms<digits> |
| Meta (Facebook) Business | @ | facebook-domain-verification=<token> |
| ACME certificate authorities (DNS-01) | _acme-challenge | <token> |
| Many SaaS tools | @ or an underscore name | <service>-verification=<token> or similar |
Always use the exact host and value from the service's own setup page. Formats change, and some services use a dedicated name such as _service-challenge.example.com rather than the root.
Step 1: Get the Token
In the service you're setting up, choose the DNS or TXT record verification method. It will show:
- Record type: TXT
- Host / Name: often
@, sometimes blank, sometimes a specific label - Value / Content: the token
- TTL: sometimes suggested, often optional
Copy the value with the copy button if there is one. Retyping long tokens almost always introduces a mistake.
Step 2: Find Where Your DNS Is Managed
Add the record at your DNS provider, which isn't always your registrar. Check your nameservers:
dig +short example.com NS
ns-512.awsdns-00.net.
ns-1024.awsdns-00.org.
ns-0.awsdns-00.com.
ns-1536.awsdns-00.co.uk.
In this example, DNS is hosted in Amazon Route 53, so that's where the TXT record goes. Adding it at your registrar would have no effect if the registrar isn't serving your DNS.
Step 3: Add the TXT Record
In a DNS Dashboard
- Choose Add record and select TXT.
- Host / Name: enter
@for the root domain. Some providers want this left blank instead. If the service asks for a subdomain such as_acme-challenge, enter just that label, not the full name. - Value: paste the token exactly.
- TTL: leave the default, or use 300 to 3,600 seconds.
- Save.
Add a new record. Don't overwrite an existing one. Your root domain probably already has TXT records, such as SPF (v=spf1 ...) or another service's verification. A name can hold any number of TXT records, and replacing your SPF record with a verification token would break email authentication.
About Quotation Marks
In DNS, TXT data is stored as one or more quoted strings. Most dashboards add the quotes for you, so you should paste the value without them. If you add quotes yourself and the dashboard adds another set, the published value contains literal quote characters and verification fails.
In a zone file, you write the quotes explicitly:
$ORIGIN example.com.
@ 3600 IN TXT "v=spf1 include:_spf.google.com ~all"
@ 3600 IN TXT "google-site-verification=rXOxyZounnZasA8Z7oaD3c14JdjS9aKSWvsR1EbUSIQ"
@ 3600 IN TXT "MS=ms12345678"
_acme-challenge 300 IN TXT "gfj9Xq_w4cLh0PmEH4Wq8x5wN8cY1pDzh1Vb2rYdXbU"
A single string can be at most 255 characters. Verification tokens are almost always shorter, so this rarely matters here.
With the AWS CLI (Route 53)
Route 53 expects TXT values to include the quotes inside the JSON, escaped with backslashes:
aws route53 change-resource-record-sets \
--hosted-zone-id Z0123456789ABCDEFGHIJ \
--change-batch '{
"Changes": [{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "example.com.",
"Type": "TXT",
"TTL": 300,
"ResourceRecords": [
{"Value": "\"v=spf1 include:_spf.google.com ~all\""},
{"Value": "\"google-site-verification=rXOxyZounnZasA8Z7oaD3c14JdjS9aKSWvsR1EbUSIQ\""}
]
}
}]
}'
Note that in Route 53, all TXT values for one name live in a single record set. An UPSERT replaces the whole set, so include your existing TXT values, such as SPF, alongside the new token or you'll remove them.
With the Cloudflare API
Cloudflare treats each TXT value as a separate record, so adding one doesn't affect the others:
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"type": "TXT",
"name": "example.com",
"content": "\"google-site-verification=rXOxyZounnZasA8Z7oaD3c14JdjS9aKSWvsR1EbUSIQ\"",
"ttl": 3600
}'
Step 4: Check the Record Is Published
Before clicking Verify, make sure the record is actually visible. Ask your authoritative nameserver first, since it reflects changes immediately:
dig @ns-512.awsdns-00.net example.com TXT +short
"v=spf1 include:_spf.google.com ~all"
"google-site-verification=rXOxyZounnZasA8Z7oaD3c14JdjS9aKSWvsR1EbUSIQ"
Then check through a public resolver, which is closer to what the service will see:
dig @8.8.8.8 example.com TXT +short
dig @1.1.1.1 example.com TXT +short
For a token at a subdomain:
dig +short _acme-challenge.example.com TXT
On Windows:
Resolve-DnsName example.com -Type TXT | Select-Object -ExpandProperty Strings
Or with nslookup on any platform:
nslookup -type=TXT example.com 8.8.8.8
Once the token appears, go back to the service and click Verify.
Waiting for a Record in a Script
When you automate verification, for example when issuing certificates or onboarding customer domains, you need to wait until the record is visible before asking the service to check it. This Bash loop polls until the token appears or a timeout is reached:
#!/usr/bin/env bash
NAME="${1:?usage: $0 name token}"
TOKEN="${2:?usage: $0 name token}"
RESOLVER="${RESOLVER:-8.8.8.8}"
for i in $(seq 1 30); do
if dig @"$RESOLVER" +short "$NAME" TXT | tr -d '"' | grep -qxF "$TOKEN"; then
echo "Found after $i attempt(s)"
exit 0
fi
echo "Not yet visible, retrying in 10s..."
sleep 10
done
echo "Timed out waiting for TXT record" >&2
exit 1
./wait-for-txt.sh example.com "google-site-verification=rXOxyZounnZasA8Z7oaD3c14JdjS9aKSWvsR1EbUSIQ"
Here's the same idea in Python using dnspython, which handles TXT records split into multiple strings by joining them:
import sys
import time
import dns.resolver
def txt_values(name: str, nameserver: str = "8.8.8.8") -> list[str]:
resolver = dns.resolver.Resolver(configure=False)
resolver.nameservers = [nameserver]
try:
answer = resolver.resolve(name, "TXT")
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
return []
return [b"".join(r.strings).decode() for r in answer]
def wait_for_token(name: str, token: str, attempts: int = 30, delay: int = 10) -> bool:
for attempt in range(1, attempts + 1):
if token in txt_values(name):
print(f"Found {token!r} on {name} after {attempt} attempt(s)")
return True
time.sleep(delay)
return False
if __name__ == "__main__":
ok = wait_for_token(sys.argv[1], sys.argv[2])
sys.exit(0 if ok else 1)
Why Verification Fails
The Record Is in the Wrong Place
The most common cause is entering the full domain in the host field, which creates example.com.example.com. Check for it directly:
dig +short example.com.example.com TXT
If the token appears there, edit the record so the host is @ or blank.
The Record Is at the Wrong DNS Provider
If you added the record at your registrar but your nameservers point elsewhere, nobody will see it. Compare the nameservers from Step 2 with the place you edited.
The Value Was Changed
Extra spaces, missing characters, an added quotation mark or a line break from copying can all cause a mismatch. Compare what dig returns with the token in the service, character for character.
A Cached Negative Answer
If the service checked before you added the record, its resolver may have cached the "no record" result. That negative answer is cached for up to the minimum TTL in your zone's SOA record, often between five minutes and an hour. Wait and try again.
Check your zone's negative caching time:
dig +short example.com SOA
ns-512.awsdns-00.net. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400
The last number is the negative caching TTL in seconds. In this example it's 86,400 (one day), which is Route 53's default, so a premature check could delay verification for a long time. Lowering that value in the SOA record is possible in Route 53, but the easier fix is simply not to click Verify until dig shows the record.
A CNAME at the Same Name
If the host you're verifying, such as www.example.com, is a CNAME, you can't add a TXT record there. Lookups follow the CNAME to its target. Verify at the root instead, or use the service's alternative method.
Should You Remove the Record Afterwards?
It depends on the service:
- Keep it for services that re-check periodically, such as Google Search Console and Google Workspace. Removing it can cause the domain to become unverified.
- Remove it for one-off challenges, such as ACME
_acme-challengetokens, which are only needed during issuance. ACME clients with DNS plugins usually delete them automatically. - Remove it when you stop using a service altogether.
Old verification records aren't dangerous in themselves, but they reveal which services you use, and over time the root TXT set can become long and messy. Some resolvers and middleboxes have trouble with very large DNS responses, so audit your TXT records from time to time:
dig +short example.com TXT | sort
Alternatives to TXT Verification
Most services offer other methods if TXT is awkward:
- CNAME record: Point a unique subdomain at a name the service controls.
- HTML file upload: Place a file at a specific path on your website.
- Meta tag: Add a tag to your home page's HTML.
DNS verification is usually the most robust, because it proves control of the whole domain and doesn't depend on your website being up or on a particular host.
FAQ: TXT Verification Records
Most DNS providers publish a new TXT record within a minute or two, and the service can verify as soon as it sees it. Delays usually come from a cached negative answer if the service checked too early.
Yes. A domain can have many TXT records at the same name, such as SPF, several verification tokens and other policies. Just make sure there's only one SPF record.
Usually @ for the root domain, or leave it blank if your provider prefers that. If the service gives a specific name such as _acme-challenge, enter just that label, because the provider adds your domain automatically.
Yes. The token is tied to your account at the service, so knowing it doesn't let anyone else verify the domain. The only thing it reveals is that you use that service.
In most dashboards, no. They add the quotes automatically, and adding your own can result in literal quote characters in the record. In zone files and some APIs, such as Route 53, you do include them.
If www is a CNAME, no other record types are allowed at that name. Verify at the root domain instead, or ask the service whether it supports verifying the CNAME target.
Conclusion
Verifying domain ownership with a TXT record is a simple challenge: the service gives you a token, you publish it in DNS, and the service looks it up. Because only someone in control of your DNS can publish it, finding the token is proof of ownership. The process is the same for nearly every service, from search consoles to certificate authorities.
Most failures come from small details: the full domain typed into the host field, the record added at the wrong provider, an existing record overwritten, or stray quotation marks. Check the record with dig before clicking Verify, keep tokens for services that re-check, and clear out the ones you no longer need.


