A client called to say that sometimes, when someone clicked through to their site from LinkedIn, they got an error page. Not always. Just sometimes. The rest of the time the site loaded fine.
If you’ve done any amount of production support, you know that “sometimes” is the worst word in the language. Here’s how that one turned out, because the answer was somewhere I wasn’t initially looking.
The setup
The site is WordPress, fronted by Cloudflare on the free plan. A few months back the client rebranded and we moved them from grow-with-coaching.com to growworks.uk.
The old domain wasn’t just switched off. That would have broken every existing link — old LinkedIn posts, email signatures, business cards, whatever Google still had indexed. Instead I did what you’re supposed to do: kept the old zone in Cloudflare, pointed its DNS at 192.0.2.1 (a reserved documentation address that goes nowhere), left the records proxied, and set up a redirect rule to forward everything to the new domain.
That’s a standard pattern and it’s a good one. Cloudflare’s redirect rules execute at the edge, before Cloudflare ever tries to contact an origin server. So you don’t need a server behind a redirect-only domain at all. The dead IP is a feature: there’s nothing to maintain, nothing to patch, nothing to pay for.
Hold that thought.
What a 522 actually tells you
The error the client was seeing was a Cloudflare 522. It’s worth being precise about what that means, because the error code narrowed the search considerably.
A 522 means Cloudflare’s edge opened a TCP connection toward the origin server and never completed the handshake within about fifteen seconds. That’s it. It is always a connection-level problem between Cloudflare and the origin.
Notably, it is not a WordPress problem. If PHP were hanging, or a database query were running long, or a plugin were stuck in a loop, you’d get a 524 — connection established, response too slow. A 522 means the conversation never started. So the entire application layer was off the table before I looked at anything.
The things I checked that weren’t it
Duplicate DNS records. The single most common cause of intermittent 522s right after a migration. If a hostname has two A records — say, the new server plus a leftover from the old one — Cloudflare round-robins between them, and every request that lands on the dead IP times out. That produces exactly the “works most of the time, randomly fails” pattern. It wasn’t this: one A record on the new domain, one CNAME for www, all clean.
Origin firewall throttling. Behind Cloudflare, your server sees all traffic arriving from a small set of Cloudflare IPs. Host-level security tools can read that as a flood from a single address and start dropping packets. This was a genuinely plausible theory, and it had an appealing story attached: LinkedIn traffic is bursty. A post gets impressions, clicks arrive in clumps, LinkedIn’s crawler re-fetches the URL for preview cards. A burst is exactly what tips a rate limiter over its threshold.
I liked that theory. It was wrong.
The LinkedIn correlation itself. Here’s the trap. Every instinct says the failures cluster on LinkedIn, therefore something about LinkedIn traffic is special. But the correlation had a much more boring explanation, and boring explanations usually win.
The actual problem
LinkedIn posts can’t be edited after publication. Every post the client made before the rebrand still contained grow-with-coaching.com links, frozen in place forever.
So “clicked from LinkedIn” wasn’t a traffic-shape problem. It was a destination problem. LinkedIn was disproportionately the source of clicks going to the old domain, because LinkedIn is where all the old links live. Anyone typing the new domain into a browser went straight to a healthy site and never saw a thing.
And the old domain, remember, points at a black hole.
The redirect-only pattern is safe for every request that matches the redirect rule. Anything that reaches the edge and falls through — a hostname the rule doesn’t cover, a path it doesn’t match, a scheme it doesn’t anticipate — gets handled by the default behavior, which is to proxy to origin. Origin is 192.0.2.1. The connection hangs. Cloudflare returns a 522.
Same error code. Completely different cause from anything I’d been investigating.
The first gap I found was the rule scoped to http.host eq "grow-with-coaching.com" — an exact match, silently excluding www.. Fixed that, and most of the failures went away.
But not all of them. And the remaining pattern was the useful one.
The detail that cracked it
Testing every combination of hostname, scheme, and path, the failures sorted cleanly:
https://→ 301, every timehttp://→ 522, every time
Not a www problem. Not a hostname problem. A scheme problem.
The redirect was implemented as a page rule, and page rule patterns include the protocol. There was a rule for https://grow-with-coaching.com/*. There was no rule for http://. Any plaintext request fell through to the dead IP and hung.
Which closes the loop on LinkedIn perfectly. Links stored in older posts were frequently http://, and LinkedIn preserves whatever scheme was originally submitted. So the traffic source with the highest concentration of old links also had the highest concentration of old schemes. The client’s “sometimes it’s broken” was really “one specific category of link is always broken, and everything else always works.”
The fix
Two changes:
- Added the missing
http://rule. The direct fix. - Enabled Always Use HTTPS on both zones. Cloudflare upgrades port-80 requests to HTTPS at the edge, which then match the existing rule normally. Both steps happen before any origin connection, so the dead IP is never dialed.
The second one matters more than the first. Adding the missing rule fixed the bug I’d found. Always Use HTTPS closes the entire class of bug — any plaintext request that slips past a pattern gets upgraded rather than falling through into the void.
One caveat worth knowing: Cloudflare warns that Always Use HTTPS can cause redirect loops if your origin also forces HTTPS. That only applies when your SSL/TLS mode is set to Flexible, where Cloudflare deliberately talks to the origin over plaintext. On Full or Full (strict), Cloudflare connects over HTTPS, the origin’s own redirect never fires, and there’s no loop. Check your mode before flipping the switch.
What I’d take away from it
The error code is a real clue — use it. 522 versus 524 eliminated the entire application stack in one step. Knowing what your infrastructure’s error codes actually mean is worth more than a dozen speculative fixes.
Correlation points at where users are, not always at what’s broken. The LinkedIn signal was real and it was useful — but it was pointing at the contents of LinkedIn posts, not at the behavior of LinkedIn traffic. I spent a while looking at rate limits because the traffic-burst story was more interesting than the frozen-links story. The boring explanation was correct.
Test the whole matrix, not the happy path. Every hostname, every scheme, with and without a trailing path, with and without a query string. The scheme split was invisible until I tested combinations I “knew” would be fine. Here’s the loop, in PowerShell — note curl.exe rather than curl, since PowerShell aliases the latter to Invoke-WebRequest, which takes entirely different arguments:
powershell
$sites = "grow-with-coaching.com", "www.grow-with-coaching.com" $paths = "/", "/about", "/?utm_source=linkedin" $schemes = "http", "https" foreach ($site in $sites) { foreach ($path in $paths) { foreach ($scheme in $schemes) { $url = $scheme + "://" + $site + $path $code = curl.exe -s -o NUL -w '%{http_code}' --max-time 20 $url Write-Host "$url -> $code" } } }
Twelve lines of output. Every one should be a 301. Anything else is a gap.
Retired domains are still production systems. The failing component wasn’t the site anyone was working on. It was the zone we’d set up months earlier and mentally filed as finished. A redirect-only domain is infrastructure, it has failure modes, and it deserves to be tested like anything else you ship.