HTTP 404 Not Found — What It Means and How to Fix It
HTTP 404 means the server understood the request but can't find the resource. It's different from 410 Gone, 301 Moved, and 500 Server Error. Here's when each status applies and...
HTTP 404 Not Found is the server’s response when it can’t locate the requested resource. The server is working, the request was received and understood, but there’s nothing at the specified URL — right now, or ever.
Use the HTTP Status Codes reference to look up all HTTP status codes with their meanings and when to use them.
What 404 means precisely
The HTTP spec (RFC 9110) defines 404 as:
“The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist.”
Key parts:
- The server is working — it processed the request and chose to respond with 404. This is different from a connection timeout or network error.
- The resource doesn’t exist at this URL — the URL is recognized as valid syntax, but nothing is there.
- Might exist in the future — 404 is ambiguous about permanence. If the resource is permanently gone and will never return, 410 Gone is more appropriate.
404 vs other “not found” responses
| Status | Meaning | Use when |
|---|---|---|
| 404 Not Found | Resource doesn’t exist (maybe temporarily) | URL is valid, resource is absent |
| 410 Gone | Resource permanently deleted | Confirmed permanent removal |
| 301 Moved Permanently | Resource moved to new URL | Redirect permanently |
| 302 Found | Temporary redirect | Redirect temporarily |
| 403 Forbidden | Exists, but access denied | Resource exists, client lacks permission |
| 400 Bad Request | Request malformed | URL or request format is invalid |
| 401 Unauthorized | Authentication required | Not logged in |
| 500 Internal Server Error | Server failed | Server error, not a client problem |
404 vs 410
- 404: “I don’t have that. It might have existed, might never have existed, might exist later.”
- 410: “I had that, it’s gone, it’s never coming back.”
Search engines treat these differently. Googlebot stops crawling 410 URLs quickly. With 404, it may retry for months hoping the content returns.
For deleted blog posts that were indexed by Google: use 410 to explicitly signal permanent deletion. For pages that may return (temporarily disabled, wrong URL entered by user): use 404.
404 in APIs vs 404 on websites
Website 404: User navigated to a URL that has no page. Return your custom 404 HTML page.
API 404:
GET /users/999where user 999 doesn’t exist → 404 ✓GET /nonexistent-endpointwhere the endpoint doesn’t exist in your API → 404 ✓GET /usersto list all users and the list is empty → 200 with empty array[], NOT 404. An empty collection exists; it’s just empty.
The last point is a common API design mistake: returning 404 when a search returns no results. A search that returns no results is a successful search — the answer is “nothing matches,” not “the search endpoint doesn’t exist.”
Common causes of 404s
On websites
- Broken internal links: You linked to
/blog/old-postbut the post was deleted or moved to/articles/old-post - URL format changes: Changing from
/blog/2024/my-postto/blog/my-postwithout redirects - Deleted content without redirects: Removing pages that other sites link to
- Incorrect relative paths:
/images/photo.jpgin production resolves to a different path than in development - Case sensitivity: Some servers treat
/Pageand/pageas different URLs; others don’t
In APIs
- Resource deleted:
GET /orders/123where order 123 was deleted - Wrong environment: Development ID that doesn’t exist in production
- Tenant isolation: Accessing a resource from another tenant’s data
- Race condition: Resource existed when the reference was created, deleted before the GET request
How to handle 404s properly
Custom 404 pages (websites)
A blank 404 page or a generic server error page loses visitors. A good 404 page:
- Confirms the page doesn’t exist (don’t pretend it does)
- Offers navigation options (search, homepage link, popular pages)
- Matches the site’s design (jarring visual breaks increase abandonment)
Measure 404 rates in your analytics. High 404 rates from organic search indicate broken links from indexed pages — find them in Google Search Console under “Coverage” → “Not Found.”
API 404 response body
Return a structured error body with enough context for the caller:
{
"error": "not_found",
"message": "User with ID 999 was not found",
"resource": "user",
"id": "999"
}
Avoid:
{"error": "Not found"} // Too vague — what wasn't found?
{} // Empty body — no useful information
Logging and monitoring
Log all 404s with the referring URL if available. A spike in 404s often indicates:
- A broken link on a high-traffic page
- A recent deployment that changed URL structure without redirects
- A third-party tool pointing to an old URL
In production APIs, 404 errors should be logged with request context (user ID, endpoint, input) for debugging.
Returning 404 in code
Node.js (Express)
app.get('/users/:id', async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) {
return res.status(404).json({
error: 'not_found',
message: `User ${req.params.id} not found`
});
}
res.json(user);
});
// Catch-all 404 handler (must be last):
app.use((req, res) => {
res.status(404).json({ error: 'not_found', path: req.path });
});
Python (FastAPI)
from fastapi import HTTPException
@app.get("/users/{user_id}")
async def get_user(user_id: int):
user = await db.get_user(user_id)
if user is None:
raise HTTPException(status_code=404, detail=f"User {user_id} not found")
return user
Python (Django)
from django.http import Http404
from django.shortcuts import get_object_or_404
# Manual:
def user_view(request, user_id):
try:
user = User.objects.get(pk=user_id)
except User.DoesNotExist:
raise Http404("User not found")
# Shortcut:
user = get_object_or_404(User, pk=user_id)
Soft 404s (Google’s concern)
A soft 404 is a page that returns HTTP 200 (success) but displays a “not found” message. Googlebot treats these as real pages, wastes crawl budget on them, and may index the error message.
Causes: custom 404 pages served with 200 status, search results pages with no results served as normal pages.
Fix: ensure all “not found” pages return HTTP 404 status code, not 200.
Related tools
- HTTP Status Codes — complete reference for all HTTP status codes
- HTTP 422 vs 400 — request error status codes explained
Related posts
- When to Use 422 vs 400 (and Other HTTP Status Code Debates) — 400 means the request is malformed. 422 means it's valid but semantically wrong.…
- HTTP 301 vs 302 Redirect — Permanent vs Temporary Redirects Explained — Understand when to use 301 (permanent) vs 302 (temporary) redirects, how they af…
- HTTP 500 Internal Server Error — What It Means and How to Debug It — HTTP 500 means the server encountered an unexpected error it couldn't handle. Un…
Related tool
Full HTTP status code reference with explanations and when to use each.
Written by Mian Ali Khalid. Part of the Dev Productivity pillar.