Soft 404s: When a Missing Page Returns 200 Instead of 404

Date:

A visitor mistypes a URL and lands on your tidy “page not found” screen. It looks correct. But if that screen was delivered with an HTTP 200, a search engine just saw a real page where there is nothing – and it may index the emptiness. The words say “not found”; the status code says “here’s content.”

The short answer: A soft 404 is a page that doesn’t exist but responds with HTTP 200 OK instead of a real 404 (Not Found) or 410 (Gone). It happens when a missing URL returns a themed “not-found” page, redirects to the homepage, or serves an empty template – all with a 200 status. Crawlers read the status code, not the words on the page, so a 200 tells them the URL is real content worth indexing. The result is wasted crawl budget, thin or empty pages in the index, and diluted site quality. The fix is to make every missing URL actually return a 404 (or 410) status, and to verify it with the status code, not by looking at the page.
A real 404 versus a soft 404Same missing page, two very different HTTP statuses
Real 404

Server returns status 404 (or 410 Gone). Crawler drops the URL, indexes nothing.

Soft 404

Missing page returns 200 – themed page, redirect, or empty template. Crawler indexes the error.

the trap

A missing page must return 404 or 410, never a 200. Status is what the crawler reads – not the words on the page.

The status code is the message

An HTTP response carries a status code that is meant for machines. 200 OK means “here is the content you asked for.” 404 Not Found means “there is nothing at this URL.” A crawler makes its indexing decision from that number, not from the visible text – so a beautifully designed “Oops, nothing here” page served with a 200 is, to a search engine, a real page whose content happens to be the words “nothing here.” The page can look like an error to a human and read as valid content to a machine at the same time.

A crawler never reads your apology copy. It reads the status line. If that says 200, the missing page is real as far as the index is concerned.

How WordPress ends up serving them

Soft 404s usually come from well-meaning choices:

  • Redirecting every unknown URL to the homepage. A plugin or a rule sends all misses to / with a 200 (or a 301 to a 200). Now every typo, every dead backlink, and every deleted post resolves to a live page, and the index fills with duplicate homepages.
  • A theme “not found” template that returns 200. Some setups render a custom 404 view but forget to send the 404 status with it, so the page is correct to the eye and wrong to the crawler.
  • Empty archives and thin auto-pages. A category, tag, or author page with no posts can return 200 with almost nothing on it – a soft 404 by emptiness rather than by error.
  • WordPress guessing what you meant. When a URL doesn’t exist, core WordPress looks for a post whose slug starts the same way and 301s to it. A mistyped link that lands on the right post is helpful; /ads.txt landing on an article about ads.txt is not, and to a crawler both are a missing page answered with someone else’s content.
  • Page numbers on a static homepage. If your front page is a static page with no pagination, /page/2/, /page/999/ and so on can still return 200 with a copy of the homepage: an endless supply of duplicate URLs.
  • Deleted content without a plan. A removed post that now shows a themed placeholder at 200, instead of a clean 404 or a 301 to a real replacement, is another soft 404.

Why it costs you

Every soft 404 spends crawl budget on a page that shouldn’t be crawled, and drops a thin or empty URL into the index that dilutes the site’s overall quality signal. For a new site trying to look substantial – to readers and to an ad or search reviewer – a scatter of indexed “not found” pages and duplicate homepages is exactly the wrong signal. It is the same failure family as a maintenance page that returns 200 instead of 503: in both cases the visible page is one thing and the status code tells the crawler another, and the status code wins.

Fixing and verifying them

The fix is to align the status with reality:

  • A genuinely missing URL must return 404 (or 410 Gone if it’s permanently removed and you want to say so). WordPress does this correctly by default; the job is usually to stop a plugin or redirect rule from overriding it.
  • Don’t blanket-redirect misses to the homepage. Redirect a moved URL to its specific replacement with a 301; let everything else 404.
  • Give thin auto-pages real content or keep them out of the index – noindex an empty archive rather than letting it sit as a 200 with nothing on it.
  • Verify with the status code, not the page. Request the URL and read the HTTP status directly (an anonymous request, since being logged in can change what you see). “It shows my nice 404 page” is not proof; “it returned a 404 status” is. This belongs in the same anonymous status-checking pass as your go-live audit, and if the site is monetized, a pile of indexed thin pages is one more thing that can stall an ad network’s review.

Design the not-found page for the human; send the 404 status for the machine. A missing page needs both, and only one of them shows up in a browser.

What we found on our own site

Before launch we ran exactly this check on bosonica.in, anonymously, against a handful of made-up and mistyped URLs. Most were fine: unknown slugs, categories, tags, authors and date archives all answered 404 with noindex, and empty categories and empty searches were already noindex. Two patterns were not:

URLBeforeAfter the fix
/ads.txt (no file yet)301 → our article about ads.txt404
/ads/, /tablepress/, /wordpress-speed/301 → the nearest-looking article404
/page/2/, /page/999/200, an identical copy of the homepage404 (title and noindex too)

Both came from WordPress core, not a plugin: the first is its guess redirect, the second its handling of page numbers on a static front page. Before turning the guessing off we checked that none of our own internal links depended on it (all 118 link targets across our posts resolved without a redirect). It takes a few lines:

// No more "nearest slug" 301s: a missing URL gets a real 404.
add_filter( 'do_redirect_guess_404_permalink', '__return_false' );

// The static homepage has no pagination, so /page/N/ is a 404 too.
add_action( 'wp', function () {
    if ( is_admin() || ! is_front_page() || ! is_paged() ) {
        return;
    }
    global $wp_query, $post;
    $wp_query->set_404();
    $wp_query->posts      = array();
    $wp_query->post_count = 0;
    $wp_query->post       = null;
    $post                 = null;
    status_header( 404 );
    nocache_headers();
    remove_action( 'template_redirect', 'redirect_canonical' ); // otherwise core 301s it back to "/"
}, 0 );
Code language: PHP (php)

Two details we only learned by testing. Setting the 404 later, on template_redirect, gave the right status but kept the homepage’s title and index robots tag, because the SEO plugin had already built the head; running on wp at priority 0 fixes that. And core then tried to 301 the paged URL back to /, which is why redirect_canonical is removed for that one request.

One more lesson from the same check: our SEO plugin’s 404 log was empty, which looked like good news. It wasn’t. The plugin’s database tables had never been created, so it had nowhere to write. A monitor that records nothing tells you nothing; check that it can record before trusting its silence.

The short version

A soft 404 is a missing page that returns HTTP 200 instead of 404 or 410 – a themed error page, a redirect to the homepage, or an empty template, all served with a success status. Crawlers read the status code, not the words, so they index the error page and you lose crawl budget and dilute the site with thin URLs. Make every genuinely missing URL return 404 (or 410), redirect only moved URLs to their specific replacements, keep empty auto-pages out of the index, and verify by reading the HTTP status of an anonymous request – never by trusting how the page looks.

Frequently Asked Questions

What is a soft 404?

A soft 404 is a URL that has no real content but responds with HTTP 200 OK instead of a 404 Not Found. It typically shows a themed “not found” page, redirects to the homepage, or serves an empty template – all with a success status – so search engines treat the missing page as real content and may index it.

Why is a soft 404 bad for SEO?

Because crawlers decide what to index from the status code, not the visible text. A 200 on a missing page gets your error page or a duplicate homepage indexed, wastes crawl budget on URLs that shouldn’t be crawled, and adds thin pages that dilute the site’s quality signals.

How do I check whether a page is a soft 404?

Request the URL and read its HTTP status code directly rather than judging by how the page looks – use an anonymous request, since a logged-in session can change what’s served. If a nonexistent URL returns 200 instead of 404, it’s a soft 404. Search Console also reports soft 404s under its index coverage.

Should a missing page return 404 or 410?

404 (Not Found) is correct for a URL with nothing at it. Use 410 (Gone) when the content was permanently removed and you want to signal that explicitly – crawlers tend to drop 410 URLs a little faster. Either is fine; a 200 is not.

Why does redirecting missing pages to the homepage hurt?

Because it turns every typo, dead backlink, and deleted post into a 200 response resolving to a live page, so the index fills with duplicate homepages instead of clean 404s. Redirect a moved URL to its specific replacement with a 301, and let genuinely missing URLs return a 404.

Share post:

Subscribe

Popular

More like this
Related

How to Merge Spreadsheets by URL (and Why Our Tool Reported 5 Misses That Weren’t)

Matching two spreadsheets on a URL column is the same job as matching a stock list to a price list by product code. It goes wrong in the same two places: how you clean the code, and how you count what didn't match.

Many-to-One: Why a Mapping Backlog Outgrows Its Target Taxonomy

You're mapping a messy source vocabulary onto a clean target taxonomy. The target has about 5,600 slots, so the job must be roughly 5,600 decisions — right? The backlog is already past 8,900, and still climbing.

The Fix That Didn’t Take: When a Content-Hash Gate Skips Your Code Change

You shipped a fix to how one field is computed, re-ran the pipeline across everything, and it reported a clean run. Then the consistency check still flags those records as wrong. The fix is in the code, the rebuild "ran" — and the records never changed. Your own optimisation skipped them.

Your Dedup Scan Is Over-Counting — Because It Forgot What You Already Fixed

The report said 80 duplicate groups still to resolve. You'd been resolving them for days. You run it again after a big batch and it *still* says 80-ish. The backlog isn't stubborn — the scan is counting work you already finished.