A December migration silently dropped six published posts. The audit I ran eight months later couldn’t see it, because an audit only examines what arrived.
In August I ran a full audit of this blog. It was thorough in the way audits are thorough. It probed 87 URLs, checked every outbound link, read the database, and produced a list of real problems:
- 49 of 66 outbound links were broken. An import had wrapped every
hrefin literal escaped-quote entities, so browsers resolved them as relative paths and 404’d. Every post from 2015 through 2025 was affected. - Permalinks were set to Plain, which means no rewrite rules existed, which is why
/robots.txt, the RSS feed and the sitemap were all returning 404s from the web server. WordPress never saw those requests at all. - The timezone was unset, so every timestamp displayed eight hours behind and anything scheduled fired eight hours late.
All of that was true, and all of it got fixed the same day. The audit also recorded the scope it was working from: 19 published posts, 4 drafts, 1 page, 27 media items, 0 comments.
That line was an accurate description of what was there. It was a completely misleading description of what should have been.

The number was 21
The site had been migrated to a new host on 26 December 2025. I found out what that migration cost on 20 August 2026 — eight months later, and one day after the audit that was supposed to have found everything.
What actually made the difference was a backup. Not a restore: a comparison. I had a full pre-migration archive sitting on a drive at home, taken minutes before the migration ran. I imported its database into a scratch database on the server, diffed the old posts table against the live one by slug with titles and content normalised, checked every apparent loss individually, and dropped the scratch database afterwards.
| Backup | Live | Lost | |
|---|---|---|---|
| Published posts | 21 | 19 | 6 |
| Drafts | 5 | 4 (different ones) | all 5 |
| Private posts | 1 | 0 | published instead, wrong date |
| Pages | 2 | 1 (the default sample page) | both |
| Attachment records | 44 | 34 (mostly new) | 38 of the 44 |
| Approved comments | 1 | 0 | 1 |
Three apparent losses turned out to be false positives — posts whose title, slug or opening content had been edited after the migration, so a diff flagged them as missing. I verified each one rather than assuming, which matters, because a method that cries wolf three times out of nine is a method you stop trusting at exactly the wrong moment.

Six published posts, gone
About 22 KB of published writing, with no row on the live site under any status or post type. Not unpublished. Absent.
Four of the six were a single series on ramping up in a new digital marketing role — first 30 days, 30 to 60, 60 to 90, plus the training post that set it up. The most obviously reusable professional writing on the site, and the piece of it a reader would be most likely to arrive at from a search.
Here is the part that kept it hidden. The site had previously used date-based URLs, and there is a rewrite rule mapping the old form to the new one. So an inbound link to one of those posts does this:
/2020/07/fundamental-digital-marketing-training/
-> 301 ->
/fundamental-digital-marketing-training/
-> 404
A working redirect into a dead end. Anything checking that the redirect rule functions gets a clean 301 and moves on. Every one of those URLs had been answering 404 since the day of the migration.
The one that actually stings
One post in the backup was published, dated July 2020. It arrived on the new site as a draft.
Eight months later — during the audit’s own draft cleanup, one day before I found any of this — I went through the drafts and trashed it, recording the reason as “no forward use.” That call was made on the understanding that it had never been published.
It had been publicly live for five and a half years.
I re-made the decision afterwards with the correct facts and the outcome didn’t change: it stays deleted, for reasons that have nothing to do with the migration. But I made a content decision on a fact the migration had invented, and I made it confidently, and the only reason I ever found out is that I happened to diff a backup the following day for an unrelated reason.
The dates were shifted, not just lost
Five surviving posts had visibly wrong dates. The pattern was systematic — each had taken the date of the item before it in the original sequence. That is what an importer does when it assigns dates positionally and the interleaved drafts and private posts it was counting on have been dropped.
When I checked properly, comparing every surviving pre-2026 post against the backup by explicit ID mapping rather than by slug, it wasn’t five. It was 16 of 16: seven wrong at the day level, nine wrong only in clock time.
The fix uses the GMT timestamp as the authority, because GMT is absolute and local time can be derived from it. That matters for the very first post on the site, whose stored local and GMT times are identical because it predates the timezone ever being set. Treating that stored value as local time would have placed it eight hours early.
Three mistakes I made doing the recovery
Worth more than the recovery itself.
The command-line tool reports success for a job it only half did. Updating a post’s date updates the local timestamp and leaves the GMT one untouched unless you pass it explicitly. After the first pass all 16 posts had correct local dates and stale UTC ones. One post showed an offset of -71,936 hours between its two timestamps. Every single command had printed Success: Updated post.
What caught it was checking an invariant instead of reading the output — every post on a site with one timezone should show the same offset between its local and GMT timestamps. One query, grouped by that offset, and the problem is either one row or it isn’t. All 29 posts now return 8.
A HEAD request said a link was dead. It was a 301 that resolves fine on a normal GET. I nearly went and edited a working link out of a post.
A 401 said an image was gone. It returns 200 to a browser user-agent. The default user-agent on the HTTP library I was using is blocked by that publisher. It had been rendering correctly for real visitors the entire time.
Two of those three “defects” were my measurement tools, not the site. That ratio is worth remembering before you act on a scan.
The files were fine. The rows weren’t.
My first reading of the 38 missing attachments was that the media needed restoring from the archive. That was wrong, and checking cost ten minutes.
Every file was already on disk, under its original upload path, including every generated thumbnail size. The file copy had come across cleanly. Only the database rows describing those files had not.
The practical effect: the restored posts had working images the moment they were inserted, because their content references absolute URLs. What was missing was the media library listing — which matters for reusing an image in the editor, and not at all for rendering a page. So the fix was to re-register the rows against the files that were already there, copying the original metadata across verbatim rather than regenerating 38 images’ worth of thumbnails on a 1 GB server for no reason.
Media library went from 34 items to 72. Nothing was copied.
What I’d tell someone doing a replatform
Your backup is an instrument, not just insurance. I think of backups as the thing you restore from in a disaster. The far more common use is this one: it is the only reference that lets you ask what should be on the site. Keep the pre-migration archive long after you are confident the migration worked, because the question it answers doesn’t come up for months.
A destination-side audit cannot find missing content. Nothing on a migrated site announces what failed to arrive. There is no error, no gap, no placeholder. The content simply isn’t mentioned, and every tool you point at the site agrees with the site.
Check the redirects resolve, not just that they fire. A 301 is not a success. Follow the chain to a 200.
Diff by ID mapping, not by slug, if anything might have been edited since. Slugs change. And verify each apparent loss individually before you believe the count.
The site is whole now: the six posts restored with their original dates and URLs, all 16 timestamps corrected, all 38 attachment records re-registered, the lost About page merged back into the new one. The standing answer to all of it is a staging clone that pulls production files and database over a locked-down key and strips every production credential on the way in — so the next time something like this is on the table, there is somewhere to run it that isn’t the live site.
Eight months is how long this was invisible. It was never going to announce itself. Something had to go looking.