← Back to the full checklist

Your missing pages return 200 OK, and it is breaking things you have not noticed

DEPLOYMENT · Debugging

This one cost two weeks on three separate sites before anyone spotted it, because every check that was being run came back green.

The symptom, if you can call it that, is nothing. The site loads. Pages return 200. Monitoring is happy. But a tool on the page quietly does nothing when you use it, and no error appears in the console.

What is actually happening

Many static hosts and single page app configurations include a catch-all rule: any path that does not match a real file gets your index.html, with status 200. That is correct and necessary for client side routing. It is a disaster for everything else.

Consider a tool that loads a dictionary at startup:

const res = await fetch('/data/words.json');
if (!res.ok) throw new Error('missing data');
const words = await res.json();

If words.json did not deploy, the fetch returns your homepage HTML with status 200. res.ok is true, so the guard never fires. Then res.json() throws a parse error inside a promise nobody awaited, and the page carries on looking perfectly normal with a tool that silently does nothing.

How to detect it in one command

Ask for something that definitely does not exist:

curl -s -o /dev/null -w "%{http_code}\n" https://yourdomain.com/zzz-does-not-exist-9912

You want 404. If you get 200, you have this problem.

Confirm by comparing checksums. If a nonsense URL and your homepage hash identically, every missing file on your site is being served as your homepage:

curl -sL https://yourdomain.com/           | md5sum
curl -sL https://yourdomain.com/zzz-9912  | md5sum

What it breaks, in order of how much it costs you

The fix

Add a real 404.html at the site root. Most static hosts serve it automatically for unmatched paths once it exists. If you genuinely need client side routing, scope the catch-all to your app routes only, and never to asset paths like /data/*, /*.txt, or /*.json.

Change how you verify. A 200 proves a request was answered. It does not prove the right thing answered. When checking that a page exists, compare its content against your homepage rather than reading its status code. That single habit would have caught this in minutes instead of weeks.

Related