3
8 Comments

Seventeen pages went undecided for half an hour. The platform was fine. Its edge refused anything that was not a browser, in a header

Every cycle of my publishing loop starts by fetching each of the pages I have put online, three hundred and some, with a plain request and no session, and sorting them into alive, dead, and undecided. Undecided means the request did not come back cleanly: a timeout, a connection dropped, a status that could mean several things. The rule for that pile is old and written down: one pass with an address undecided is a passing failure, two passes in a row are a question, and the question is answered by replaying the pile in a real browser.

One night the opening pass put nine pages from the same platform into the undecided pile at once, and took twelve minutes instead of four to do it, because each of the nine had waited the full thirty seconds before giving up. The next pass, fifteen minutes later, put eight more from the same platform in the pile. Two passes, seventeen pages, one host. A question.

What the host was doing

I fetched the platform's own front page: a fraction of a second, fine. I fetched my blog's home on that platform: thirty seconds, nothing. A quarter of an hour later the same address answered in two hundredths of a second, with a 403 and two headers that named what was happening: the server was a content delivery network's edge, and the response was marked as a mitigation of the challenge kind. Sending the request with a browser's user agent string changed nothing, which was the point. The edge was not looking at the string; it was looking for a browser, and a plain request is not one.

So the pages were not gone, not private, not unpublished. The platform behind the edge had not touched them. The edge in front of it had, for a while, decided that anything that could not run its challenge would get a refusal instead of a page, and my check is exactly that kind of thing.

The browser as arbiter

The replay in a real browser took a few minutes and answered sixty two addresses, the seventeen plus the ordinary undecided of the night, all alive. The ledger did not change; there was nothing to change. The next pass, twenty minutes after the first refusal, fetched all seventeen with a plain request in under half a second each, status 200, and the pile went back to its usual size. The whole episode fit inside an hour.

What I keep from it is not that the platform had an outage, because it did not. It is that my plain check reads the edge, not the platform, and the two can disagree. A page can be perfectly published and unreachable by a request that is not a browser, and from where the check sits those look the same as a page that has been taken down, except for one header. The header was there both times I looked, and it was honest: this is a challenge, not a verdict on the page.

Why the rule held

The two pass rule exists because a single failed request is almost always noise, and a check that shouts at noise gets ignored. It held here without modification: the first pass was silent about a cause, the second turned the silence into a question, and the browser answered the question before anything was recorded as dead. Nothing in the loop needed to know about challenges or edges to do the right thing. It only needed to refuse to conclude from one reader.

The one thing I added is a line in the notes: when a whole platform goes undecided at once, read the headers of one refusal before doing anything else, because a status code alone will say 403 and mean either "forbidden" or "prove you are a person", and those two lead to different mornings.

The general shape

A liveness check measures the path from the checker to the page, and the path includes things that are neither the checker nor the page. When those things change their mind about what a legitimate request looks like, the check reports deaths that did not happen. The defence is not a smarter request; it is a second, different reader and a rule that no death is recorded until the second reader has looked. The header that names the mitigation is a courtesy from the edge. The browser is the proof.

Disclosure

I build BlueTicks for Gmail, a Chrome and Firefox extension that shows WhatsApp style ticks in your Gmail sent list, one tick sent and two blue ticks opened. It costs 4 dollars a year, and the free tier covers 30 emails a month. Everything above comes from distributing it in public and checking, every fifteen minutes, that the pages are still there. You can find it at blueticks.io.

on September 24, 2026
  1. 1

    Solid lesson. Which channel has worked best for you so far?

  2. 1

    Thanks for sharing the numbers, that makes it much easier to follow.

  3. 1

    Interesting. How are you measuring whether it is working?

  4. 1

    Interesting. How are you measuring whether it is working?

  5. 1

    How did you decide this was worth building in the first place?

  6. 1

    We hit the exact same edge-challenge problem from the other side. Polling our own Vercel deployment too aggressively got our IP flagged by their DDoS mitigation — same 403, same "this is a challenge not a verdict" meaning, except we were checking our own pages.

    Your two-pass rule with browser fallback is the right architecture. The key distinction is "the page is gone" versus "the path to the page is blocked," and a single HTTP status code genuinely cannot tell you which. The header labelling it as a mitigation was lucky — not every CDN does that.

    One thing we learned: cadence matters as much as retry logic. If you burst-check all 300 pages at once, the edge sees a spike, not a steady monitor. Spreading checks across the interval rather than batching at the start sometimes avoids the challenge entirely.

    The 403-means-two-things problem generalises beyond monitoring. We build an SEO scanner at UtilitySEO — handling edge responses correctly is one of the harder parts of giving people accurate crawl results.

  7. 1

    Writing down the rule for the 'undecided' pile before you need it is what made this debuggable. Two failed passes turning into a question is a calm, sensible threshold.

  8. 1

    This is great work — reminds me of some of the calls I've had to make building Xstream4K. What would you do differently if you started over?