1. Library
  2. Http and the Web
  3. Status Codes

Updated 10 hours ago

You clicked a link. You expected to arrive somewhere. You arrived nowhere instead.

That's a 404. It's the Internet's way of saying "I looked, but there's nothing here." Not an error in the catastrophic sense—more like knocking on a door and finding the apartment empty. The building exists. The address is valid. But whoever you came to see isn't home. Maybe they moved. Maybe you got the wrong number. The 404 doesn't know.

What the Server Is Actually Saying

When a server returns 404 Not Found, it's making a specific claim:

  • I understood your request
  • I searched for what you asked for
  • It doesn't exist at this address
  • I don't know if it ever existed or ever will
GET /blog/that-post-you-bookmarked HTTP/1.1

HTTP/1.1 404 Not Found

That last point matters. A 404 is inherently uncertain. The server isn't saying "this was deleted" or "this never existed"—just "it's not here now."

Why Things Go Missing

Typos happen. Someone types /usr/profile instead of /user/profile. One character, completely different destination.

Content moves. That blog post existed for three years, then got reorganized into a new URL structure. The old link still circulates.

Things get deleted. Products discontinued, accounts closed, posts removed. The URL becomes a ghost.

Links break. Someone linked to your site five years ago. You've redesigned twice since then. Their link points to an architecture that no longer exists.

IDs don't exist. GET /api/users/99999—but there is no user 99999. Never was.

404 vs. 410: Uncertainty vs. Certainty

This distinction matters more than it seems.

404 Not Found means "I don't know what happened to this":

GET /blog/some-post

HTTP/1.1 404 Not Found

Search engines see this and think: maybe it'll come back. I'll check again later.

410 Gone means "this existed and was deliberately removed":

GET /blog/retracted-article

HTTP/1.1 410 Gone

Search engines see this and think: remove it from the index. It's not coming back.

Use 410 when you know. Use 404 when you don't.

404 vs. 403: Missing vs. Forbidden

Another crucial distinction:

404 means the resource doesn't exist. 403 Forbidden means it exists but you can't have it.

GET /api/users/123/private-data

HTTP/1.1 403 Forbidden

Sometimes you'll see 404 used instead of 403 deliberately—to hide whether a resource exists from unauthorized users. If an attacker can't tell the difference between "doesn't exist" and "exists but forbidden," that's a security advantage.

The Soft 404 Problem

This is a common mistake that causes real harm:

GET /deleted-page

HTTP/1.1 200 OK

<html>
  <body>Sorry, this page doesn't exist!</body>
</html>

The page says "not found" but the status code says "success." This confuses search engines. They index your "page not found" message as if it were real content. Your SEO suffers. Users find your error pages in search results.

Always return an actual 404 status code for missing content. The body can be friendly. The status code must be honest.

Turning Dead Ends into Directions

A 404 page is a moment of friction. The user wanted something and didn't get it. What you do next determines whether they leave frustrated or find their way.

The minimum: Tell them what happened and give them a way home.

<h1>Page Not Found</h1>
<p>We couldn't find what you're looking for.</p>
<a href="/">Go to homepage</a>

Better: Give them options.

<h1>That page doesn't exist</h1>
<p>Maybe one of these will help:</p>
<ul>
    <li><a href="/">Homepage</a></li>
    <li><a href="/products">Products</a></li>
    <li><a href="/support">Support</a></li>
</ul>
<form action="/search">
    <input type="text" name="q" placeholder="Search...">
    <button type="submit">Search</button>
</form>

The goal: transform "you're lost" into "here are some paths forward."

API Responses

APIs need to be specific about what wasn't found:

GET /api/users/99999

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

{
    "error": "User not found",
    "code": "USER_NOT_FOUND",
    "userId": 99999
}

Clients can then handle this meaningfully:

async function getUser(userId) {
    const response = await fetch(`/api/users/${userId}`);

    if(response.status === 404) {
        return null;
    }

    if(!response.ok) {
        throw new Error(`Request failed: ${response.status}`);
    }

    return response.json();
}

When to Redirect Instead

If the content moved rather than disappeared, redirect—don't 404:

GET /blog/old-url

HTTP/1.1 301 Moved Permanently
Location: /blog/new-url

This preserves the user's journey and tells search engines where the content went. Use 404 only when there's genuinely nothing at that address and nowhere to redirect to.

Monitoring the Gaps

Every 404 is information. Someone expected something to be there—either because they mistyped, because something broke, or because you moved something without redirecting.

Log your 404s:

app.use(function(request, response, next) {
    response.on('finish', function() {
        if(response.statusCode === 404) {
            logger.warn({
                url: request.url,
                referrer: request.headers.referer,
                timestamp: new Date()
            });
        }
    });
    next();
});

Look for patterns. If the same URL appears repeatedly, something is linking to it. If the referrer is your own site, you have a broken internal link. If it's external, consider adding a redirect to wherever that content went.

The Human Reality

A 404 is a broken promise. Someone clicked expecting to arrive somewhere. Your job is twofold: tell them honestly that the destination doesn't exist, and help them find where they actually want to go.

The technical implementation is simple. The human consideration is what separates a dead end from a helpful redirection.

Frequently Asked Questions About 404 Not Found

Was this page helpful?

😔
🤨
😃