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:
| Class | Meaning | Monitor's default reaction |
|---|---|---|
| 1xx | Informational (rare) | Usually invisible; keep waiting |
| 2xx | Success | Probably healthy — but verify the body |
| 3xx | Redirect | Depends: expected or a silent break? |
| 4xx | Client error | 404 / 403 usually means down or misconfigured |
| 5xx | Server error | Down. This is what you're watching for |
Note
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
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.
| Assertion | Example | Catches |
|---|---|---|
| equals | data.status equals "ok" | A health endpoint reporting degraded |
| exists | data.results exists | An empty or malformed response |
| gt / lt | data.queueDepth lt 1000 | A backlog building up unnoticed |
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.