Skip to main content
Builder’s Journal

How to Remove Old URLs From Google Without Breaking Your Live Site (410 vs 404)

· · 4 min read

How to Remove Old URLs From Google Without Breaking Your Live Site (410 vs 404)

If a domain used to host a different site, Google will keep crawling hundreds of dead URLs for months — and a plain 404 barely slows it down. Serving HTTP 410 Gone instead tells search engines the pages are permanently gone and gets them dropped far faster. The danger: if your current site shares the old URL pattern, a blanket 410 rule can declare live pages "gone" too. The safe version only emits 410 for URLs WordPress has already resolved as a 404, and only within the old site’s date range.

What you’ll learn: the practical difference between 410 and 404 for de-indexing, and a guard that removes legacy URLs fast without ever risking a live page.

Evidence note: based on cleaning up my own domain, which previously ran a dated-permalink blog. The code below is the live implementation; status codes were confirmed by request.

The situation

My domain used to host a completely different site — a sports/news auto-blog on dated permalinks like /2023/08/14/some-old-post/. Those posts are long gone and return 404, but months later search engines were still crawling the stale index entries, cluttering logs and analytics with requests for content that no longer exists.

410 vs 404 — why it matters for de-indexing

  • 404 Not Found means "I couldn’t find this right now." It’s ambiguous — the page might come back — so search engines keep the URL in the index and re-crawl it for a long time.
  • 410 Gone means "this existed and is permanently removed." It’s an explicit, unambiguous signal, and engines drop 410 URLs from the index noticeably faster than 404s.

For pages you know are never coming back — a previous site’s entire URL set — 410 is the right code.

The trap: my live site uses the same URL pattern

Here’s what makes this dangerous. My current WordPress site also uses dated permalinks (/2026/07/31/...). So a naive rule — "return 410 for anything matching /YYYY/MM/DD/" — would have declared my live posts permanently gone and actively removed them from Google. That’s a self-inflicted SEO disaster.

Any blanket pattern match on the URL is unsafe when the old and new sites share a structure.

The fix: two guards so it can never touch live content

The rule has to be impossible to misfire. Two conditions:

  1. Only act on an actual 404. Run after WordPress has resolved the request. A live post resolves to HTTP 200 and never reaches this code; only genuinely-missing URLs do.
  2. Restrict to the old era. The legacy blog ran up to a certain year, so only emit 410 for dates in that range. Anything newer that happens to 404 (a typo in a current URL) stays a recoverable soft 404 — never marked permanently gone.
add_action( 'template_redirect', function () {
    if ( ! is_404() ) return;   // guard 1: live pages (200) never get here

    $path = wp_parse_url( $_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH );
    if ( ! $path ) return;

    // Legacy dated permalink: /YYYY/MM/DD/...
    if ( ! preg_match( '#^/((?:19|20)d{2})/d{2}/d{2}/#', $path, $m ) ) return;
    if ( (int) $m[1] >= 2025 ) return;   // guard 2: current era stays a soft 404

    // Force 410 and hold it even if the 404 template re-asserts 404.
    add_filter( 'status_header', function ( $sh, $code ) {
        return $code === 404
            ? ( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1' ) . ' 410 Gone' )
            : $sh;
    }, 10, 2 );

    status_header( 410 );
    nocache_headers();
    // The 404 template still renders the body — only the status code changes.
}, 1 );

Set the year cutoff (2025 here) to whatever separates the old site’s content from yours.

One subtlety: WordPress’s 404 template runs after this and can re-assert a 404 status. That’s why there’s a status_header filter as well as the direct status_header( 410 ) call — it holds the 410 through template rendering. The visitor still sees your normal 404 page; only the HTTP status differs, which is exactly what search engines read.

Verifying it

Three checks, by requesting real URLs and reading the status line:

URLExpectedWhy
A known dead legacy URL (/2023/08/14/...)410 GoneOld era + genuinely 404 → permanently gone
A live current post (/2026/07/31/...)200 OKResolves normally, never hits the 410 code
A made-up current URL (/2026/07/31/nope/)404 Not Found404 but current era → stays recoverable

All three behaved as expected on my site. curl -sI https://YOUR_SITE/2023/08/14/old-post/ | head -1 is all you need to confirm the first one.

A reusable checklist

  1. Decide the cutoff date that separates the old site from yours.
  2. Only emit 410 after is_404() — never on a pattern match alone.
  3. Restrict the 410 to the legacy date range; leave current-era 404s recoverable.
  4. Hold the 410 through the 404 template with a status_header filter.
  5. Verify all three cases: legacy → 410, live → 200, fake-current → 404.
  6. Optionally submit the old URLs for removal in Search Console to speed it further.

Takeaway

410 gets dead URLs out of Google faster than 404 — but it’s a permanent statement, so the whole risk is accidentally aiming it at live pages. When your old and new sites share a URL structure, guard the rule twice: act only on a real 404, and only within the old era. Then a repurposed domain sheds its past without you ever gambling a live page.

AI assisted with drafting; the domain history, the guard logic, and the status-code checks are from my own site.

Share this article

Leave a Reply

Your email address will not be published. Required fields are marked *