> ## Content Index
> Fetch the complete content index at: https://llitd.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# How I Retire Blog Posts Without Breaking a Single Link
- URL: https://llitd.com/retire-blog-posts-without-breaking-links/
- Published: 2026-10-23T14:00:00.000Z
- Updated: 2026-10-23T14:00:00.000Z
- Description: Merging 26 thin pages into one, 20 dead slugs, and a link checker that found its own bugs first: how I retire blog posts with 301 redirects and lose nothing.
- Author: Lincoln Cole
- Tags: Blogging, Ghost CMS, #LincolnCole

📚

📚 Want a real book to hold and support my work? Check out [Real Books here](https://llitd.com/books).

My parents' hiking blog used to have twenty-six pages that were, collectively, a terrible idea.

City of Columbus parks, letter A. City of Columbus parks, letter B. All the way to Z. Each one a thin list of the parks starting with that letter, each one its own URL, each one indexed by Google. When we ran a quality pass over that blog, twenty-three of the worst-scoring pages were those letter pages. The tempting fix was to expand all twenty-six into proper articles. That would have been twenty-six times the work to produce twenty-six pages nobody wanted to read.

The real fix was to retire them. One directory page replaced all twenty-six, and every old letter URL now answers with a permanent redirect to it. Try it: [the letter A page](https://hiking-ohio.com/city-of-columbus-parks-letter-a/?ref=llitd.com) lands you on [the Columbus city parks directory](https://hiking-ohio.com/columbus-city-parks-directory/?ref=llitd.com). Nothing broke. Nobody who had bookmarked letter M hit a dead end.

That is what this post is about: how to retire, merge or rename blog posts without throwing away the links, rankings and readers they collected. I run three Ghost blogs and have done this enough times now to have opinions, a firm order of operations, and two pieces of tooling I built after getting it wrong.

> "Cool URIs don't change." — Tim Berners-Lee

> "Plan to throw one away; you will, anyhow." — Fred Brooks

---

## Why posts need retiring, and why delete is the wrong verb

Every blog that lives long enough accumulates pages that should not exist anymore. In my case they come in four flavors.

**Thin siblings.** The letter pages. A set of small pages that only make sense together, competing with each other for the same searches and each too slim to rank. One strong page beats twenty-six weak ones.

**Superseded serial pages.** I used to publish my serialized fiction one episode per URL. Episode one of a story here, episode five there, a prologue on its own page. When the books were finished and the series got proper hub pages, those episode URLs had no reason to stand alone.

**Typos that went live.** A park name misspelled in a slug. A tag with a doubled suffix. Once a URL is public, someone may have linked it, and fixing the spelling breaks their link unless you catch it.

**Things that simply ended.** A free-this-week promotion, an old landing page for a game that moved.

Here is the problem with deleting any of those. A URL is a promise. Somebody bookmarked it, linked to it from a forum, or found it in search. Delete the page and that promise turns into a 404, and whatever ranking the page earned walks out the door with it. Tim Berners-Lee's note [Cool URIs don't change](https://www.w3.org/Provider/Style/URI?ref=llitd.com) made this argument in 1998, and it has not gotten less true. My house rule is blunt about it: deletion requires a destination. Anything I unpublish or merge leaves a redirect to the page that now serves the same reader.

## Build, redirect, then unpublish

The order matters more than anything else in this post, so I will give it to you first and explain it after.

1. Build and publish the destination page.
2. Write the redirect rules and get them live.
3. Check that each old URL really lands on the new page.
4. Only then unpublish the old pages.

Do it in any other order and you open a window where real people hit 404s. Unpublish first and the old URLs die before anything catches them. Redirect first, to a page you have not published, and you are redirecting people to nothing. For the hiking directory, the directory went live first, with its twenty-six rules written alongside it. The letter pages stayed up until those redirects were live, and only then came down.

### The redirect rules, and the 302 trap

Ghost keeps redirects in [a single redirects file](https://ghost.org/help/redirects/?ref=llitd.com). I write mine as small JSON fragments, one rule per old URL, then merge them in. Here are three real rules from this site:

```json
[
  { "from": "^/the-dark-citadel-episode-1/?$", "to": "/dark-citadel/", "permanent": true },
  { "from": "^/world-on-fire/?$", "to": "/world-on-fire-complete-series/", "permanent": true },
  { "from": "^/ravens-peak-epilogue/?$", "to": "/ravens-peak/", "permanent": true }
]

```

The `from` is a regular expression. The `^` and `$` anchor it to the whole path, and the `/?` lets it match with or without a trailing slash, so one rule catches both forms.

Now the trap. Look at `"permanent": true`. Leave it off and Ghost answers with a 302, a temporary redirect, not a 301\. A 302 tells search engines the old URL is coming back, so they have far less reason to treat the new page as its replacement. [Google's own guidance on redirects](https://developers.google.com/search/docs/crawling-indexing/301-redirects?ref=llitd.com) is that a permanent server-side redirect is the clear signal the destination should be treated as the canonical page. On the hiking directory, I went back and marked all twenty-six letter rules permanent before the letter pages came down. A redirect can work perfectly in a browser and still be the wrong kind, and you will not notice unless you look at the status code.

✏️

The browser lies to you here. A 301 and a 302 look identical when you click a link: you end up on the right page either way. Check the status code with curl or your browser's network tab, every time.

Here is the check, run against one of the rules above. The `-I` asks for the headers only, which is all you need:

```bash
curl -sI https://llitd.com/the-dark-citadel-episode-1/

```

The two lines that matter in the answer are these:

```
HTTP/1.1 301 Moved Permanently
Location: /dark-citadel/

```

A 301 and a `Location` that names the page you meant. If the first line says 302, the rule is missing its permanent flag. If it says 404, the rule is not live at all, and you should not unpublish anything yet. If it says 200 with no `Location` at all, nothing redirected: that URL is still serving a page of its own, so find out which one before you move on. Run that one line for a handful of old URLs every time you upload a redirects file. It takes thirty seconds.

There is one more line in that answer worth reading: `Cache-Control: public, max-age=31536000`. That is a year. A browser that has seen a permanent redirect can keep following it from its own cache without asking the server again, so a wrong 301 is not something you can quietly take back the next morning. That is the real reason the check comes before the unpublish, not after.

### Never rebuild the file from memory

The other trap is that Ghost takes the redirects file whole. Upload a new file and every rule it had is replaced. If you write your new file from an old copy, or from scratch, you silently delete every redirect you forgot.

So my merge tool only appends. It reads the file exactly as Ghost serves it, refuses any new rule whose `from` already exists, writes the merged file in the same format, and then reads its own output back to prove every pre-existing rule is still there, unchanged and in order, and that the count went up by exactly the number of rules I added. If any of that fails, nothing gets uploaded. Download the live file, append, verify, upload. Every time.

## Twenty dead slugs on this site, and where each one went

Here is the honest inventory from llitd.com. Twenty old URLs on this blog carry a redirect rule, and as of October 2026 every one of them answers with a 301\. They fall into a few patterns worth stealing.

**Episodes go to their series hub.** `/the-dark-citadel-episode-1/` goes to `/dark-citadel/`. Episodes one and five of Last Light in the Dark both go to [the Last Light in the Dark page](https://llitd.com/last-light-in-the-dark/). A reader who followed an old link to one episode wanted that story, and the hub is where the story lives now.

**Prologues go to the closest thing that replaced them.** The Raven's Peak prologue now points at its audio preview page, because that is the version of the prologue that exists today. The epilogue and the "after" page go to the book's hub.

**Old series URLs go to the complete-series page.** `/world-on-fire/` now lands on the World on Fire complete series guide, which is what anyone typing that address actually wants.

**Ended promotions go to what outlasted them.** A free-this-week page for Graveyard of Empires now lands on the book's own page, because the promotion ended and the book did not. An old sample page, from back when I offered early chapters under a different name, now points at the page where readers sign up to read my next book before it launches. Same reader, same want, the current offer.

**Orphans go to the best page you have, and you admit it.** One old URL, `/the-long-dark/`, redirects to my books page, and I do not love that rule. A catch-all redirect is better than a 404, but it is the weakest kind of redirect: point a pile of old URLs at one generic page and a search engine can reasonably decide none of them has a real replacement. Use it rarely and on purpose.

The trick is to ask one question per URL: what did the person who clicked this want, and which page that exists today gives it to them? The homepage is almost never the answer.

## The guards I built after getting it wrong

Rules on a page do nothing at the moment you are rushing. So two of these rules now live in code.

### A slug rename is refused without a redirect

Renaming a published post's slug is the easiest way to create a 404 without noticing. You fix a typo in the URL, the post looks fine, and every old link to it is now dead. My sync tooling refuses to push a slug change on a published post unless a redirect for the old slug is recorded. It also finds every other post on the blog that links to the old slug and offers to rewrite those links, so my own internal links do not lean on a redirect forever. The hiking blog carries three fixes of exactly that kind: a park slug that spelled Battelle with one L too few, a tag that spelled state as "stae", and a tag Ghost had suffixed with a stray -2 when a duplicate was made. Each is now a permanent redirect to the corrected address instead of quiet breakage.

### A link checker that admits when it cannot tell

Redirects protect the URLs I own. They do nothing for the links inside my posts that point elsewhere: a cited article that vanished, an image on a CDN that moved. So I built a checker that walks every post body and tests every link.

It reports four states, never two. OK. BROKEN, which means a 404 or a 410 and nothing else. DRAFT-UNPUBLISHED, for a link to one of my own posts that is written but not live yet, so the link is right and just early. And COULD-NOT-TELL, for everything ambiguous: a 403 from a site that blocks bots, a 429, a server error, a timeout. A refusal is not a deletion. Plenty of perfectly alive sites refuse automated requests, and a checker that calls those dead will have you deleting good links.

Here's the problem I did not expect. The checker kept finding bugs in itself before it found them in the blog, and every one of them was a confident false 404\. Filenames with spaces got cut off at the first space, which produced thirty-seven fake broken images on its very first run, every one of which loaded fine. A filename with "(2)" in it got cut off at the first closing bracket. Three links still carried `&amp;` where the browser would send a plain ampersand. Markdown's backslash escapes went out inside the request, and that alone reported thirteen perfectly good photos on one hiking post as broken. It was also probing URLs inside code blocks, which are examples, not links.

Once the checker was honest, what it found was real: my own dead links now point at their replacements, and third-party links that had moved were updated to the live page or removed.

One thing I've learned from that: run a new checker against pages you know are good before you trust what it says about pages you think are bad. A checker's first findings are often about itself.

## Get out there and give every dead URL a home!

Here is the whole process, in the order I would do it on your blog this week.

Pull your list of URLs before you change anything, from your sitemap or your analytics. Open Google Search Console's [page indexing report](https://support.google.com/webmasters/answer/7440203?ref=llitd.com) and look at the pages it reports as not found: each one is a URL somebody still asks for. Decide, page by page, whether it gets merged, rewritten or retired. Build the destination first. Write permanent redirects, append them to a fresh copy of your live redirects file, and check the status codes yourself. Unpublish last.

If you run Ghost, I wrote up [how I use Ghost CMS as a full publishing platform](https://llitd.com/ghost-cms-full-publishing-platform/), which covers the rest of the setup these redirects live inside, and [what actually works for blog SEO](https://llitd.com/ghost-cms-seo-ideas-for-blogs/) if you want the bigger picture.

Retiring a post is not admitting failure. It is gardening. Cut what is thin, merge what belongs together, and leave a path behind you so nobody who followed an old link ever finds a dead end.

[Browse My Books](https://llitd.com/books)