We inherited a catalogue of 151 browser games from a working, live website. Every one of them had been playable at some point. We ran a link check across all of them expecting a handful of casualties.
What actually breaks
Almost all of it was abandoned free hosting. GitHub Pages subdomains that used to serve a game and now return the default "There isn’t a GitHub Pages site here" page. Someone puts a game up, moves on, deletes the repository, and the link rots quietly. Nobody notices because nobody is checking.
- Dead GitHub Pages subdomains — by far the largest category
- Object storage buckets that were emptied or made private
- Expired TLS certificates on small self-hosted sites
- One domain that had lapsed entirely and no longer resolved
The 403 problem
The interesting failures were the ambiguous ones. A 403 can mean two completely different things, and treating them the same will either leave dead games in your catalogue or delete working ones.
Object storage answers a removed or misconfigured bucket with a short XML body containing an AccessDenied code. That game is gone, and a browser sees exactly the same thing. But a bot filter also answers 403 — with an HTML interstitial that a real visitor clears without noticing. That game is fine.
Identical status code, opposite meaning. The only way to tell is to read the body.
What we do about it
A checker runs daily. A 404 or a storage-denied 403 retires the game immediately. An expired certificate needs two consecutive sightings before it counts, because that is the kind of thing that gets fixed. Timeouts and 5xx never retire anything — a host having a bad afternoon should not cost it its place.
Nothing is ever deleted. A retired game is flagged inactive, so if the URL comes back the checker can restore it, and anyone who favourited it does not lose the reference.