HTTP 404 Not Found

Not Found

HTTP 404 is Not Found: the server has nothing to serve at that URL, or won’t say whether it does. RFC 9110 section 15.5.5 defines it as the origin server not finding “a current representation for the target resource” or not being “willing to disclose that one exists”. It doesn’t say whether the absence is temporary or permanent.

What sends it

Your app, for an unknown id or a deleted row; a static server with no file at that path; and the framework’s fallback when no route matches. Express answers Cannot GET /path, Go’s ServeMux answers 404 page not found, and Express also gives 404, not 405, when the path exists but the method doesn’t. An API should still say what was missing:

HTTP/1.1 404 Not Found
Content-Type: application/problem+json

{"type": "about:blank", "title": "Not Found", "status": 404, "detail": "No order with id 4182"}

That body is RFC 9457 problem details. A client shouldn’t retry a 404 unchanged; it should fix the URL or treat the thing as gone.

Caching

404 is one of the codes RFC 9110 lets a cache store heuristically, without any Cache-Control; the tool’s card says “Cacheable by default”. So a CDN may keep answering 404 for a short while after you publish the page. CloudFront caches 4xx and 5xx responses for 10 seconds by default. nginx’s proxy_cache goes the other way: proxy_cache_valid 10m; stores only 200, 301 and 302, so a 404 needs its own proxy_cache_valid 404 1m;. For URLs that are about to exist, send Cache-Control: no-store with the 404.

404, 410, 403 or a 200

  • 410 Gone says the resource was removed on purpose and isn’t coming back. RFC 9110 prefers it over 404 when the server knows that.
  • 403 Forbidden says the resource exists but this caller can’t have it. RFC 9110 allows 404 instead to hide that it exists; GitHub’s API does this for private repositories.
  • A 200 “page not found” template is a soft 404. Google flags those in Search Console. Single-page apps produce them when every path falls back to index.html with a 200.

Two AWS surprises run the other way: S3 answers 403 AccessDenied for a missing key when the caller lacks s3:ListBucket, and API Gateway REST APIs answer 403 Missing Authentication Token for a path that doesn’t exist.

Search engines

Google removes URLs that return 404 from its index and, by its own documentation, treats every 4xx except 429 the same way, so 410 isn’t a stronger signal there. A 404 for /robots.txt means “no rules”: Google crawls the whole site.

In your stack

// Express: registered after every route, so it runs only when nothing matched
app.use((req, res) => res.status(404).json({ error: 'not_found' }));
error_page 404 /404.html;    # serves the page and keeps the 404

Leave out the = in error_page 404 = /index.html;: it takes the target’s status, so every missing URL becomes a 200 and a soft 404.

order = get_object_or_404(Order, pk=pk)            # Django, or raise Http404()
abort(404)                                         # Flask
raise HTTPException(status_code=404)               # FastAPI
http.Error(w, "order not found", http.StatusNotFound) // or http.NotFound(w, r)

Try it in the HTTP Status Codes

404 Not Found

  1. 404Not Found4xx Client error

    Nothing lives at that address, and saying so is safe: the server does not have to admit whether anything ever did.

    When to use it

    An unknown path, a deleted row, an id that never existed.

    Common mistakes

    Hiding a permission failure behind it, or the reverse, and returning it for errors that are the server’s own fault.

    Cacheable by defaultRFC 9110

Every code, by class

Codes marked unofficial come from one server’s own convention — nginx, Cloudflare, cPanel, Microsoft — and are not in the IANA registry.

Runs in your browser. Nothing you enter is sent anywhere.