Skip to content

A 200 Is Not Proof (The Redirect That Eats Your gclid)

CONVERSION TRACKING
A 200 Is Not Proof (The Redirect That Eats Your gclid)
Conner Crowe

Five redirects on a client’s site were quietly deleting a quarter of his advertising measurement, and every check anyone had run on them came back green.

The redirects were correct. Each one returned a 301 to exactly the page it should have. Click an ad, land on the right article, read it, call the office. Nothing to see.

What they dropped was the query string.

What the query string is carrying

When somebody clicks a Google ad, Google appends a gclid to the destination URL. That parameter is the click. It’s how a conversion recorded on your site gets matched back to the keyword, the ad, the device, the auction it came from. Microsoft uses msclkid the same way. Your UTM tags ride in the same place.

All of it lives after the question mark, and a redirect that rebuilds the destination URL from scratch throws all of it away.

Here’s the shape of the failure:

Ad final URL:  /old-page/?gclid=EAIaIQ...&utm_source=google&utm_medium=cpc
Redirect:      301 -> /new-page/
Browser lands: /new-page/

Status code 301. Correct target. And every conversion that visitor generates arrives in Google Ads attached to nothing, because the identifier that would have attached it was discarded one hop before the page loaded.

Why nobody catches it

Because everything a normal check looks at passes.

Open the ad’s final URL in a browser and you get the right page. Run a crawler over the site and the redirect resolves 200 with no chain and no loop. Look at Google Ads and the ads are approved, the landing page experience is fine, and the destination isn’t flagged. Look at analytics and traffic is arriving.

The only symptom is negative. Conversions get counted, but a growing share of them sit in campaigns and keywords that appear to have produced nothing. If the account is on automated bidding, the bidding is learning from a signal with a hole in it, and it’ll quietly pull budget toward the segments whose conversions still make it home.

On the account I found this on, the affected redirects sat under 860 of the 3,519 paid clicks in a thirty-day window, for months. The traffic was fine. The measurement was gone.

The check

One line, and it works on any platform.

curl -s -o /dev/null -w "%{http_code} -> %{redirect_url}\n" \
  "https://example.com/old-page/?gclid=TEST123&utm_source=google&utm_medium=cpc"

You want the parameters back out the other side:

301 -> https://example.com/new-page/?gclid=TEST123&utm_source=google&utm_medium=cpc

If redirect_url comes back bare, that redirect is eating your click identifiers. If there’s more than one hop, run it again against each hop, because the loss can happen anywhere in the chain.

For the final destination rather than the hop, follow the redirects and print where you ended up:

curl -s -o /dev/null -L -w "%{url_effective}\n" \
  "https://example.com/old-page/?gclid=TEST123"

I ran this against every landing page on a site migration last week before letting a single ad repoint at it. The cutover script refuses to run otherwise. A 200 is a precondition, not the test.

Where it comes from

Four sources, in the order I find them.

Hand-written server rules. Both Apache and nginx keep the original query string by default, right up until the replacement target carries a ? of its own, and then they silently stop. In Apache, a RewriteRule whose substitution introduces its own query string replaces the incoming one unless you add the QSA flag. In nginx, rewrite preserves the arguments by default but return 301 /path; does not, so a return needs a trailing $is_args$args. The rule that looks tidiest, a literal destination with its own tracking parameters bolted on, is the one that drops the click.

WordPress redirect plugins. Several of them normalize the destination and rebuild it clean. This is the most common source I see, because the plugin is doing what it thinks is tidy.

Marketing vanity URLs. The /promo that forwards to the real page, set up in a registrar’s forwarding panel years ago by somebody who has left. Domain forwarding at the registrar level almost never preserves the query string.

Host-level redirect files. Netlify _redirects and Cloudflare Pages rules do preserve the query string by default, but a rule that hardcodes its own query parameters replaces rather than merges.

The rule I’d write down

Never accept a status code as proof that a URL is good. Verify that the thing you care about came out the other end.

That’s the same discipline as checking a form delivers mail rather than that it returned a success panel, and checking a tag fired rather than that the container published. The identity layer of The Tracking Stack is entirely made of parameters that survive or don’t, and a status code never tells you which.

A 200 tells you a server answered. It tells you nothing about what it answered with.

Want a review like this on your account?

Want this kind of review
on your account?

Thirty minutes on the phone. Same person on the call as on the work. Walk out with a clear set of next steps.