Monitoring 1017 min read

Why a 200 OK Can Still Mean Your Site Is Broken

The status code is the easiest thing to check and the easiest to fool. Here's how to monitor what your page actually returned.

The Ensju team
On this page

Here's a failure mode that catches good teams off guard: the monitor is green, the dashboard is all 200s, and the site is completely broken. A deploy shipped a blank page, an error page that returns 200, or a "we'll be right back" placeholder — and because the status code was technically a success, nothing paged anyone.

The short version

  • A status code reports what the server decided to say, not whether the page is correct.
  • Plenty of broken states return a 200: blank pages, error screens, maintenance placeholders, wrong-content responses.
  • Verify the response body — a keyword that must appear (or must not) — to catch what the code can't.
  • For APIs, assert on the JSON itself, not just the 2xx.
  • Watch redirects too: an unexpected 301/302 can silently break a critical path.

The 200 trap

An HTTP status code is a three-digit number the server chooses to send. It's a claim, not a measurement. When your app throws an unhandled exception, a well-behaved framework returns 500 — but plenty of setups catch the error, render a friendly page, and return 200 with it. A CDN serving a stale or empty cache entry returns200. A soft paywall or a login wall returns 200. In every case the naïve monitor that only checks "is the code 2xx?" reports perfect health.

A quick status-code refresher

The five classes, and what each should mean to a monitor:

ClassMeaningMonitor's default reaction
1xxInformational (rare)Usually invisible; keep waiting
2xxSuccessProbably healthy — but verify the body
3xxRedirectDepends: expected or a silent break?
4xxClient error404 / 403 usually means down or misconfigured
5xxServer errorDown. This is what you're watching for

Note

"Expect a 2xx" is a fine default, but it's a floor, not a ceiling. The interesting failures live inside a 200.

Why a 200 lies

Concretely, all of these return a 200 OK while being unmistakably broken:

  • The blank deploy. A build error ships an empty <body>. The server returns the (empty) file with a 200.
  • The soft error. The app catches an exception and renders "Something went wrong" — as a 200.
  • The maintenance placeholder. A "back soon" page put up during a deploy, never taken down, quietly serving 200s to real users.
  • The wrong content. A routing bug serves your marketing homepage where the app should be, or a login screen where authenticated content belongs.
  • The bot challenge. A WAF or anti-bot layer serves a "checking your browser" interstitial — often a 200 or 403 — instead of your actual page.

Content and keyword checks: the fix

The cure is simple and cheap: don't just check that the page loaded, check that it contains what a working page contains. A keyword check passes only if the response body includes (or excludes) a string you specify.

  • Must contain a marker that only the correct page has — a product name, a "Sign out" link that proves you're logged in, a specific heading.
  • Must not contain a string that only a broken page has — "Something went wrong", "Under maintenance", "Just a moment...".

Pick a keyword that's deep in the page's real content, not something in the header or footer that renders even on an error page. "Add to cart" is a better signal than your company name.

Tip

A keyword the monitor can only see when everything worked — a value that requires the database, the template, and the auth all to be healthy — is worth ten shallow checks.

The redirect trap

Redirects are their own quiet hazard. A 301 or 302 is often exactly what you want (HTTP→HTTPS, apex→www). But an unexpected redirect can break a critical path invisibly:

  • An expired session redirecting your API monitor to a login page — which returns 200.
  • A misconfigured rule bouncing users to the wrong region or a parked domain.
  • A redirect loop that a lenient monitor follows into a timeout.

Decide per monitor whether you expect the final page or the redirect itself, and check for the one you mean. If a URL should not redirect, an unexpected 3xx is a failure worth knowing about.

Checking API responses, not just codes

APIs make the "200 isn't enough" problem sharper, because a JSON endpoint can return a perfectly-formed 200 whose payload says something is wrong. The upgrade is a JSON assertion: check a specific path in the body.

AssertionExampleCatches
equalsdata.status equals "ok"A health endpoint reporting degraded
existsdata.results existsAn empty or malformed response
gt / ltdata.queueDepth lt 1000A backlog building up unnoticed
Assertions turn “the API answered” into “the API answered correctly”.

This is the difference between monitoring that your service is reachable and monitoring that it's right. In Ensju, HTTP monitors support both keyword matching and JSON-path assertions for exactly this reason.

What good looks like

A well-configured HTTP monitor checks, in order:

  • The connection succeeded and TLS is valid (see certificate monitoring).
  • The status code is the specific one you expect for that endpoint.
  • The response arrived within your time budget.
  • The body contains the "healthy" marker and none of the "broken" ones.
  • For APIs, the JSON payload asserts true on the fields that matter.

Layer those and a green check actually means something. For the wider picture of what to watch and how often, start with what uptime monitoring is.

Frequently asked questions

Is a 200 status code enough to prove my site is working?
No. A 200 only means the server chose to return a success code. It can still serve an empty page, an error rendered as a 200, a maintenance placeholder, or a login screen where content should be. To prove it's really working, verify the response body too — a keyword or a JSON field.
What status code should a monitor expect?
Usually 200 for a normal page. But a health endpoint might return 204, an API might return 201 on create, and a URL you expect to redirect should return 301 or 302. The rule is: expect the specific code that means healthy for that endpoint, not just “any 2xx”.
How do I monitor an API's JSON response, not just its status?
Add assertions on the response body: check that a specific JSON path equals, contains, or exists — for example that data.status equals "ok". That upgrades the check from “the endpoint answered” to “the endpoint answered correctly”.
Why does my monitor see a different result than my browser?
Usually because the monitor isn't logged in, doesn't run JavaScript, or is being served a bot-challenge or geo-variant your browser isn't. Matching a keyword that only appears in the correct, fully-rendered response is how you catch that mismatch.

Get the next one in your inbox

Practical, vendor-neutral monitoring tips — what to watch and how to alert on it — plus the occasional Ensju update. No spam, unsubscribe anytime.

Monitoring tips + Ensju updates. No spam, unsubscribe anytime.