Keeping HTML File Verification Alive Through a Cutover

Problem Statement

File-based verification survives everything except the one event you are planning. A token at /google1a2b3c.html works perfectly until a blanket redirect rule sends every legacy path to the new domain — at which point the file returns 301 instead of 200, and a 301 does not verify. The property unverifies itself days later, silently, and you find out when somebody tries to open the Change of Address form. This page sits under Property Verification Methods.

How a blanket redirect breaks file verification A request for the verification file passes through the redirect rule and returns 301 to the new domain instead of the 200 the verifier requires, so the claim lapses. The Rule Catches Its Own Verification File Verifier requests /google1a2b3c.html location / rule matches everything 301 returned not the 200 required Claim lapses Nothing errors and nothing alerts — the property simply stops being yours to administer. Re-checks run on their own schedule, so the lapse surfaces days after the deploy.
The failure is delayed, which is why it is almost never attributed to the redirect deploy that caused it.

When to Use This Approach

  • The property is a URL-prefix property verified by an HTML file or a meta tag, and you cannot switch it to DNS today.
  • You are about to deploy a catch-all redirect on the legacy host.
  • The legacy origin is being decommissioned and the token lives on it.
  • An agency or a former team member verified the property and you need the claim to survive without their involvement.

The Class of Bug, Not the Instance

This failure belongs to a family worth naming, because a migration produces several members of it at once: paths that must keep answering while everything around them redirects. Verification files are one. The ACME challenge path used for certificate renewal is another. A payment provider’s callback URL, an app-store association file, a mail-provider’s proof file, and a partner’s health-check endpoint are all the same shape.

Every one of them shares three properties. They are invisible in normal use, so no smoke test covers them. They are consumed by a machine that will not report the failure to you. And they fail later — days after the deploy that broke them — so the change that caused the breakage has already been forgotten by the time the symptom appears.

Treating them individually produces the same incident repeatedly with a different path each time. Treating them as a class produces one carve-out list, held in the repository next to the redirect rules, asserted in the deploy pipeline, and reviewed whenever a new integration is added.

# One list, asserted before every redirect deploy — this is the durable fix
for p in /google1a2b3c.html /.well-known/acme-challenge/probe /callbacks/payments/health; do
  code=$(curl -s -o /dev/null -w '%{http_code}' "https://legacy.example$p")
  [ "$code" = "200" ] || echo "CARVE-OUT BROKEN ($code): $p"
done

The rest of this page treats the verification file specifically, because it is the member of the family that a migration is most likely to depend on — but the carve-out list is the thing worth building.

Step-by-Step Instructions

1. Find Every Verification Asset You Depend On

# The legacy host's verification file must return 200 today and after the cutover
curl -sI https://legacy.example/google1a2b3c.html | head -1

# The meta-tag route, if that is what was used
curl -s https://legacy.example/ | grep -o '<meta name="google-site-verification"[^>]*>'

2. Carve the Verification Paths Out of the Redirect Rule

An exact-match location placed above the catch-all is the smallest safe change. Nginx matches exact locations before prefix and regex locations, so ordering within the file does not matter — but keeping it visually first documents the intent.

# Exact match wins over the catch-all regardless of position — keep it first anyway
location = /google1a2b3c.html {
    root /var/www/verification;   # served from a directory the migration does not touch
}

location ^~ /.well-known/ {
    root /var/www/verification;   # ACME and friends need the same exemption
}

location / {
    return 301 https://example.com$request_uri;
}

The Apache equivalent uses a RewriteCond exclusion rather than a location block:

# Exclude the verification file and .well-known from the catch-all rewrite
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/google1a2b3c\.html$
RewriteCond %{REQUEST_URI} !^/\.well-known/
RewriteRule ^(.*)$ https://example.com/$1 [R=301,L]

3. Re-inject the Meta Tag Where the Template Is Gone

If the legacy application is being switched off and replaced by a redirect service, there is no template left to carry a meta tag. Inject it at the edge instead, so the claim does not depend on the application.

# Serve a minimal verified page for the root while everything else redirects
location = / {
    add_header Cache-Control "no-store";
    return 200 '<!doctype html><html lang="en"><head>
<meta name="google-site-verification" content="TOKEN_HERE">
<title>Moved</title></head><body>Moved to example.com</body></html>';
    default_type text/html;
}
Three ways to keep the claim alive, compared A matrix comparing path carve-out, edge injection and migrating to DNS on effort, durability through origin decommissioning, and whether it survives a domain change. Pick the One That Survives Your Plan APPROACH EFFORT SURVIVES ORIGIN LOSS LONG TERM Carve out the path minutes no stopgap Inject at the edge an hour yes good bridge Move claim to DNS one record yes the right answer Do the DNS record today and the other two rows become optional rather than urgent.
The carve-out buys a week; the DNS record removes the problem from every future migration.

4. Add the DNS Claim Anyway

Whatever stopgap you deploy, add a DNS TXT token in parallel. Two live claims cost nothing, and the DNS one is immune to origin changes, redirect rules, template rewrites and consent banners — the argument set out in Verifying a GSC Property with DNS TXT Records.

Worked Example

A university department moved dept.legacy-university.example under university.example/dept/. The property had been verified in 2017 with an HTML file, and the cutover plan sent every legacy path to the new structure with a single rewrite rule.

The redirect deploy went out on a Tuesday. Nothing appeared to be wrong: traffic moved across, the redirects were single-hop, and the coverage reports looked healthy on both properties. The following Monday the legacy property showed ownership verification failed, and with it went the ability to file the Change of Address the plan depended on.

Recovery took twenty minutes but had to happen under pressure:

  1. An exact-match location was added above the catch-all, restoring a 200 for /google2f3e4d.html.
  2. Verification was re-run from the interface, which succeeded immediately because the file had never actually changed — only its status code had.
  3. A DNS TXT token was added the same afternoon so the claim no longer depended on any HTTP behaviour at all.
  4. The runbook gained a pre-deploy check asserting a 200 on the verification path, borrowed from the validation pattern in Writing a Migration Go-Live Runbook.

Verification

# 1. The carve-out works: 200 for the token, 301 for everything else
curl -sI https://legacy.example/google1a2b3c.html | head -1     # expect 200
curl -sI https://legacy.example/anything-else       | head -1     # expect 301

# 2. The token content is unchanged — a rewritten body fails verification too
curl -s https://legacy.example/google1a2b3c.html

# 3. Belt and braces: the DNS claim resolves independently of HTTP
dig +short TXT legacy.example | grep google-site-verification

Run check one from a machine outside your network. An internal proxy or a split-horizon DNS entry can serve the file correctly to you and not to anyone else, which produces the most confusing version of this failure — verified from the office, unverified from the internet.

Pre-deploy checklist for verification survival Four checks to run before deploying a catch-all redirect: verification path returns 200, well-known path returns 200, DNS token present, and a second owner verified. Four Checks Before the Redirect Deploy Token path returns 200 from outside .well-known returns 200 ACME keeps working DNS token already present independent claim Second owner verified too no single point All four are one-line assertions — cheap enough to run in the deploy pipeline itself. A failing assertion blocks the deploy instead of surprising you a week later.
Turning these into pipeline assertions is what stops the failure recurring on the next migration.

FAQ

Why does a 301 fail verification when the file is still there? The verifier requires the token to be served at the exact URL it was issued for, with a 200. A redirect means the URL no longer answers for itself, and the check treats that as the file being gone — which, from the perspective of that host, it is.

Can I just re-verify after the cutover instead? Only if the file is reachable, which is precisely what the cutover broke. Re-verification runs the same check, so it fails for the same reason until the carve-out or the DNS token is in place.

Does the same problem affect meta-tag verification? Yes, and more often, because the tag lives in a template that a migration usually replaces. It also fails when the legacy application is switched off in favour of a redirect service — there is no page left to carry the tag.

Is there any downside to keeping both a file and a DNS token? None. Search Console accepts any live claim, multiple claims coexist, and holding two means a single change never leaves you locked out. The only cost is remembering to remove both when you genuinely want to relinquish the property.

What about the .well-known path — is that related? Not to verification, but it fails in exactly the same way and usually in the same deploy. A catch-all redirect that swallows /.well-known/acme-challenge/ breaks certificate renewal on the legacy host, which is the subject of Issuing Certificates Before DNS Cutover.

How quickly does a property unverify after the file stops answering? Not immediately. The re-check runs on its own schedule, typically within a few days, which is why the failure is almost never attributed to the deploy that caused it. That delay is the strongest argument for asserting the carve-out in the pipeline rather than relying on noticing.

Can I keep the file but change its contents? No. The token content is checked as well as the status code, so a file rewritten by a template change or replaced by a generic placeholder fails just as a 301 does. Serve it from a directory the build does not touch.

Does an edge worker count as serving the file? Yes — the verifier cares only that a request to that URL returns 200 with the right body. Generating the response at the edge is a perfectly valid way to keep the claim alive after the origin is gone, and it is more durable than a file on a server somebody may decommission.

Is it worth carving out the path permanently, or only during the migration? Permanently. The exemption costs nothing, and the same catch-all rule tends to be reintroduced years later by somebody who does not know the history. A carve-out that lives in the repository next to the rule it protects is self-documenting in a way that a runbook note is not.

What if we have several verification files from different services? Put them all in one directory and carve out the directory rather than the individual paths. That way adding a new service’s file is a copy rather than a config change, and the carve-out list stays short enough that people keep it accurate.

Related

← Back to Property Verification Methods