200 OK Does Not Mean Your Service Works
If you have ever built a health check, you have probably written something close to this: const res = await fetch ( url , { method : ' GET ' , signal : AbortSignal . timeout ( 10000 ) }); const isUp = res . status === 200 ; I ran a version of that for a while. It is wrong in at least five ways, and every one of them bit me while building an outage tracker for Indian services. This is a write-up of what actually breaks, because most monitoring tutorials stop at the snippet above. 1. The server answers, the service is dead The single biggest gap. 200 OK tells you a server returned a response. It tells you nothing about whether the thing a user came to do still works. A bank homepage can render in 400ms while UPI payments from that same bank are failing at the switch. Different systems, different teams, different failure modes. Your check is green and the feature is on fire. You cannot fully solve this from outside. What you can do is stop treating a 200 as proof of health, and stop displaying it as one. 2. 403 is not down Plenty of sites block automated requests deliberately. Bot protection, WAF rules, rate limits, geo rules. In India this is common on high-value government and travel portals. IRCTC is the obvious example. A naive checker marks these down permanently. Users learn to ignore your tool inside a week. 403 means the server is alive and refusing your specific request. That is different information from 500 , and treating them the same throws away the distinction that matters most: Code Server state What it tells a user 200 Alive, responded Little. The feature may still be broken. 401 / 403 Alive, refusing this request Usually nothing about the outage. Often your check being blocked. 404 Alive The path is wrong, not the service 429 Alive, rate limiting you You are the problem, back off 500 / 502 / 503 Broken, overloaded, or in maintenance Genuine signal 504 Something upstream did not answer Genuine signal, usually a dependency Timeout / DNS failure Unknown A