Why the blog went silent
This blog has been quiet since 2014. Not because I stopped tinkering — the homelab kept growing (TrueNAS, UniFi, Proxmox/KVM, solar automation) — but because life happened and the site sat through a WordPress migration that never quite finished. This month I finally brought it back, and the process of auditing a 12-year-old migrated blog turned up enough lessons to be worth writing down.
If you’ve ever moved a WordPress site between hosts, domains, or servers, most of this will look familiar.
The audit: what I found
I connected an AI agent to the site through WordPress’s REST API and did a full inventory: 45 published posts, 4 pages, 52 tags, 16 categories. That sounds healthy. It wasn’t. Here’s what a migration leaves behind:
1. Duplicate posts
The migration had re-imported content without deduplicating, so five posts existed twice with identical titles — Google Chromecast, iPad 2 review, clearing Linux cache, Linux disk quotas, and SSH brute-force protection with iptables. Both copies were live, both were indexed. The fix: pick the newer revision of each pair and trash the other. Trash, not delete — WordPress keeps it recoverable.
2. A media library that lost its media
Every content image pointed at the old domain, wongtek.com, which no longer resolves. The media library itself was empty — zero items. The posts carried dead <img> tags from 2012. Lesson: check your media library after any migration, not just your posts.
3. Internal IPs published to the internet
This one mattered. Two pages still linked to http://192.168.10.5:30040/... — my LAN IP and a port, published to the world over plaintext HTTP. These were broken links for visitors and free reconnaissance for anyone curious. Migration tools love to bake absolute internal URLs into content. Grep your database for RFC1918 addresses:
grep -rn "192.168.\|10\.0\.\|172.1[6-9]\.\|172.2[0-9]\.\|172.3[01]\." wp-content/
4. Taxonomy sprawl
52 tags for 45 posts, including gems like USB3, USB 3.0, and UCSD 3.0 — the last one being a typo of the second. Also VirtualBox/Virtual Machine/vm, and cars/Civic/Honda Civic. Twelve tags had zero posts. Merging tags on old content is tedious; deleting unused ones takes seconds.
5. Placeholder junk pretending to be content
“Hello world!” and the default Sample Page were still live, twelve years in. Fresh installs always ship these; migrations always keep them.
How I checked every link
I wrote a small auditor that pulls every post and page through the REST API, extracts every href and img src, and probes each target:
curl -s "https://example.com/wp-json/wp/v2/posts?per_page=100&_fields=id,link,content"
After the cleanup the site went from 45 posts to 39, with zero 404s in the content. The audit script re-runs any time I change anything.
REST API quirks worth knowing
Two things cost me time:
- Trashing via REST:
POST /wp-json/wp/v2/posts/123 {"status":"trash"}fails withrest_invalid_param—trashisn’t in the status enum. UseDELETE /wp-json/wp/v2/posts/123withoutforce=trueinstead; that moves it to trash. - Pages are not posts: the same ID can exist in both namespaces. Trashing a page through the posts endpoint silently does nothing.
Application passwords, not admin passwords
For API access I created a WordPress application password (Users → Profile → Application Passwords) instead of using the admin password. It’s revocable in one click, works over basic auth, and is scoped to one user. If you’re wiring scripts or agents to WordPress, this is the way:
curl -u "username:xxxx xxxx xxxx xxxx xxxx xxxx" \
https://example.com/wp-json/wp/v2/users/me?context=edit
What’s next
The site is clean now: 39 posts, no duplicates, no dead images, no internal IPs leaking, taxonomy pruned. The bigger job is the 12-year gap itself — so I’m aiming to publish something every other week from here on. The homelab has been busy: a self-hosted AI agent on KVM, MCP integrations into TrueNAS and UniFi, and home battery automation with Home Assistant. Those stories are coming.
Same practical tone as the old posts. Same promise: only commands I’ve actually run.
