X Xerobit

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...

Mian Ali Khalid · · 6 min read
Use the tool
HTTP Status Codes
Full HTTP status code reference with explanations and when to use each.
Open HTTP Status Codes →

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

StatusMeaningUse when
404 Not FoundResource doesn’t exist (maybe temporarily)URL is valid, resource is absent
410 GoneResource permanently deletedConfirmed permanent removal
301 Moved PermanentlyResource moved to new URLRedirect permanently
302 FoundTemporary redirectRedirect temporarily
403 ForbiddenExists, but access deniedResource exists, client lacks permission
400 Bad RequestRequest malformedURL or request format is invalid
401 UnauthorizedAuthentication requiredNot logged in
500 Internal Server ErrorServer failedServer 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/999 where user 999 doesn’t exist → 404 ✓
  • GET /nonexistent-endpoint where the endpoint doesn’t exist in your API → 404 ✓
  • GET /users to 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-post but the post was deleted or moved to /articles/old-post
  • URL format changes: Changing from /blog/2024/my-post to /blog/my-post without redirects
  • Deleted content without redirects: Removing pages that other sites link to
  • Incorrect relative paths: /images/photo.jpg in production resolves to a different path than in development
  • Case sensitivity: Some servers treat /Page and /page as different URLs; others don’t

In APIs

  • Resource deleted: GET /orders/123 where 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:

  1. Confirms the page doesn’t exist (don’t pretend it does)
  2. Offers navigation options (search, homepage link, popular pages)
  3. 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 posts

Related tool

HTTP Status Codes

Full HTTP status code reference with explanations and when to use each.

Written by Mian Ali Khalid. Part of the Dev Productivity pillar.