Skip to main content

WordPress Post-Go-Live Cleanup SOP

Purpose

Define the cleanup pass that runs after the WordPress Post-Go-Live Validation Checklist confirms the new site is functional, and before the site enters monitoring/maintenance mode. The validation checklist asks "does the new site work?"; this SOP asks "have we removed everything that shouldn't be there?" — leftover drafts, template seed pages, parked migration content, redundant plugins, stale sitemap pointers.

Skipping cleanup is the #1 source of post-launch SEO defects, duplicate-content penalties, and "why is this old page still indexed?" tickets weeks later.

Scope

Applies to every WordPress site that goes live on the SC stack, regardless of whether it was a new build, a migration from another platform (Duda, Wix, Squarespace), or a rebrand of an existing site. The template-derived nature of the SC stack (Astra + Elementor + LeadConnector + SearchAtlas + Auctollo) means there is always cleanup work to do.

Trigger

Run after WordPress Post-Go-Live Validation Checklist reaches T+24h sign-off. Complete within 7 calendar days of cutover.

Pre-conditions

  • Validation checklist signed off (DNS, SSL, forms, sitemap public, email subdomain verified)
  • Go-live baseline backup captured (per validation checklist §T+24 hr)
  • WP admin access for the executor with role Administrator
  • Hosting provider file-manager access (for .htaccess and wp-content inspection)
  • SearchAtlas / OTTO admin access for the client account

Defined output

When this SOP completes:

  • No orphaned draft pages remain that aren't either (a) actively being worked on with a named owner + target publish date, or (b) explicitly retained as template-seed reference
  • No -old / migration-leftover URLs publishing publicly
  • No WordPress default content (Sample Page, Hello World post, default categories) live on the site
  • Exactly one sitemap plugin is generating the public sitemap; conflicting plugins are explicitly disabled
  • robots.txt points at the canonical sitemap URL (verified via curl, not via admin claims)
  • 301 redirects in place for every URL the cleanup removed or moved
  • A Page Inventory record exists in the client's Drive folder mapping every published URL to its source, owner, and last-touch date

Procedure

Step 1 — Take a cleanup-baseline snapshot

Before deleting anything, capture an export so accidental deletions are recoverable beyond WP Trash retention.

  • WP admin → Tools → Export → choose All content → download the XML
  • Save to Drive: Client_Delivery/<client-slug>/wp-backups/YYYY-MM-DD-pre-cleanup-export.xml
  • Confirm the XML is non-zero size and opens in a text editor (sanity check)

Step 2 — Audit draft pages with a strategy

Drafts on a production WordPress install fall into four categories. Each category has a different correct action.

CategoryWhat it looks likeAction
Template seedGeneric placeholder ("Sample Page", default Privacy Policy, demo About)Always trash. Templates ship with these so dev has a starting point; they have no business living on production
In-progress new contentNewer iteration of a published page (e.g., revised attorney bio not yet approved)Keep — but require a named owner + target publish date. Drafts without an owner > 90 days = trash candidates
Old content snapshotPrevious version of a page that the team rolled forward and parked as backupTrash. Git/DB backups are better archives than draft pages
Test / iteration"Practice Areas v2", "Home (test layout)", duplicate-with-tweaksTrash after diffing against the live version

Procedure

  1. WP admin → Pages → click the Drafts filter to see the draft count and list
  2. Watch for the hierarchical-parent trap. If a parent page is in Draft status, its CHILDREN may also be hidden from the standard Pages view even though they're published. Cross-check via the REST API:
    curl -s "https://<domain>/wp-json/wp/v2/pages?per_page=100&_fields=id,slug,status,link,parent" \
    | python3 -m json.tool | grep -E '"id"|"link"|"status"'
    Any published page in the REST output that doesn't appear in your admin Pages list is hidden by the parent-status trap. Open it via /<domain>/wp-admin/post.php?post=<ID>&action=edit.
  3. For each draft, decide its category from the table above
  4. For drafts you'll keep: tag them with the owner's name in the title or add an editor note in the page content (e.g., [OWNER: <owner-name> | TARGET: 2026-06-15]) so the next quarterly audit knows who to ping
  5. For drafts you'll trash: select all → Bulk Actions → Move to Trash → Apply
  6. Do not Empty Trash yet. Trash retention is your safety net. See Step 8.

Step 3 — Find and resolve the -old / migration-leftover URL set

A common migration footprint: the dev team renamed original pages by changing their parent slug to <thing>-old (e.g., attorneys-old, practice-areas-old) to free up canonical slugs for new content. The renamed pages stay Published and publicly reachable but invisible in the admin hierarchical view because their -old parent is a Draft.

Procedure

  1. Curl the public sitemap and grep for any -old, -backup, -v1, -draft, -staging patterns in URLs:
    curl -s "https://<domain>/sitemap.xml" | grep -oE 'https://[^<]+'
    curl -s "https://<domain>/page-sitemap.xml" | grep -oE 'https://[^<]+' | grep -E '-old|-backup|-v[0-9]+|-staging|-draft'
  2. For each suspicious URL: verify it's a real WordPress page via REST API (slug query):
    curl -s "https://<domain>/wp-json/wp/v2/pages?slug=<the-slug>&_fields=id,slug,status,link,parent"
  3. For each confirmed legacy URL, choose one of two paths:
    • Path A (preferred when the -old content is the live, only-existing content for that entity): Re-parent the page to its canonical parent. In WP admin → open the page → Page Attributes → Parent → switch to the canonical parent. WordPress rebuilds the URL automatically. Add a 301 redirect from the old URL to the new (see Step 6).
    • Path B (when a canonical replacement already exists and is better): Trash the legacy page. Add a 301 redirect from the legacy URL to the canonical (Step 6).
  4. Slug-conflict check before any re-parent. If a draft already exists under the new parent with the same slug, WordPress will append -2 and create a broken URL. Resolve by either trashing the draft first or changing one of the slugs.
  5. After all -old pages are resolved, trash their now-empty parent pages (attorneys-old, practice-areas-old, etc.)

Step 4 — Remove WordPress default template-seed content

Every fresh WP install ships with content that has no business on a client production site.

  • Sample Page (/sample-page/) — trash it
  • Hello World post (/hello-world/ or first post) — trash it
  • Default "Uncategorized" category — rename if used, otherwise leave (cannot be deleted)
  • Default WP admin user (admin, wpuser) — if present and unused, delete and reassign content to a named user
  • Default tagline (in Settings → General: "Just another WordPress site") — replace with the client's actual tagline or empty
  • WordPress logo / placeholder favicon — replace with the client's brand asset per SC Design Kit
  • Default wp-content/uploads/<year>/<month>/ placeholder images (sample images that ship with starter themes) — audit and remove any that aren't referenced by published pages

Step 5 — Verify plugin state (don't trust the admin UI alone)

"Disabled" in the WP admin Plugins screen does not always mean the plugin's frontend hooks have stopped firing — PHP opcode cache and partially-loaded hook registrations can keep injecting output. Verify with curl before declaring a plugin off.

  • For each plugin you've disabled, curl a public page and check for the plugin's frontend signature:
    curl -sI "https://<domain>/?cb=$(date +%s)" | grep -iE 'x-[a-z]+-otto|x-[a-z]+-cache|x-pingback'
    curl -s "https://<domain>/?cb=$(date +%s)" | grep -oE '<meta name="[^"]+"|<!-- [A-Z]+SEO|<!-- Google Tag'
  • Signals that a "disabled" plugin is still active:
    • Plugin-specific response headers (x-metasync-*, x-yoast-*, x-rank-math-*)
    • HTML comments in <head> referencing the plugin
    • JavaScript or CSS assets loading from the plugin's URL path
  • If a plugin is still firing despite being deactivated: deactivate again, then clear the site's PHP opcache (hosting panel) or restart PHP-FPM via host support. Re-verify with curl.

Step 6 — Sitemap plugin conflict check

The SC stack can end up with multiple sitemap generators competing for /sitemap.xml: SearchAtlas's sitemap module, Auctollo "XML Sitemap Generator for Google", and WordPress core's /wp-sitemap.xml. Pick ONE and disable the others.

  • Curl every plausible sitemap URL and record which return 200:
    for u in /sitemap.xml /sitemap_index.xml /wp-sitemap.xml /page-sitemap.xml /post-sitemap.xml /sitemap-misc.xml ; do
    code=$(curl -sI -o /dev/null -w "%{http_code}" "https://<domain>$u")
    echo "$code $u"
    done
  • Identify which plugin owns each 200-responding URL by reading the XML header comment (Auctollo signature: sitemap-generator-url='http://www.arnebrachhold.de')
  • Disable all but one. Default for the SC stack as of 2026-05: Auctollo owns /sitemap.xml. See Auctollo Sitemap Setup SOP for the explicit disable steps for SearchAtlas's competing sitemap module.
  • Verify robots.txt advertises the surviving sitemap URL (not a stale 404 pointer):
    curl -s "https://<domain>/robots.txt" | grep -i sitemap
  • Submit the surviving sitemap URL to Google Search Console; remove stale GSC submissions

Step 7 — Set up 301 redirects for every removed/moved URL

Any URL the cleanup made unavailable needs a 301 redirect. Without redirects you lose link equity from external sites that already point at the old URLs, and Google takes longer to update its index.

  • Install the Redirection plugin (Tools → Redirection) if not present
  • Add one rule per removed/moved URL:
    • Source: the old URL path (e.g., /attorneys-old/roland-moots/)
    • Target: the canonical replacement (e.g., /attorneys/roland-moots/)
    • Match type: URL only
    • Action: 301 Permanent Redirect
  • Bulk-import via CSV if there are more than ~10 rules
  • Verify each rule:
    curl -sI "https://<domain>/<old-path>" | grep -E 'HTTP|location'
    Expect HTTP/2 301 + location: https://<domain>/<new-path>

Step 8 — Set generous trash retention

Default WordPress trash retention is 30 days. Increase to give yourself a longer recovery window for the inevitable "wait, that was supposed to stay published" moment.

  • Hosting file manager → edit wp-config.php
  • Add (or update) the line:
    define( 'EMPTY_TRASH_DAYS', 90 );
  • Save and verify by opening any trashed page in admin — should show "Permanently delete in 90 days" or similar

Step 9 — Create the Page Inventory record

A one-time inventory pays for itself the first time someone asks "is this page still in use?"

  • Create a Google Sheet at Client_Delivery/<client-slug>/client-reference/page-inventory.gsheet
  • One row per published page, columns:
    • URL
    • Page title
    • Page ID
    • Source (template-seed / migrated / new-build / partner-CR / etc.)
    • Owner (named individual responsible for content)
    • Last-touch date
    • Notes (special handling, references, dependencies)
  • Generate the initial inventory via REST API:
    curl -s "https://<domain>/wp-json/wp/v2/pages?per_page=100&_fields=id,slug,link,title,modified" \
    | python3 -c "import json,sys; [print(f\"{p['id']}\t{p['link']}\t{p['title']['rendered']}\t{p['modified']}\") for p in json.load(sys.stdin)]"
  • Paste into the sheet, fill in Source + Owner + Notes manually for each row

Step 10 — Sign-off

  • All draft pages either trashed or carry an owner + target publish date
  • No legacy -old / migration URLs publicly serving
  • WordPress default content removed
  • Plugin state verified via curl (no orphan signatures from "disabled" plugins)
  • Exactly one sitemap plugin generating the public sitemap; robots.txt matches
  • All removed URLs have 301 redirects in place; spot-checked via curl
  • EMPTY_TRASH_DAYS set to 90 (or per project preference, but not the default 30)
  • Page Inventory created in client Drive folder
  • Evidence: screenshot of the updated Drafts view (showing only owned/in-progress drafts), curl HEAD output of all removed URLs showing 301 status, and a link to the Page Inventory sheet — all attached as a completion comment on the cleanup ClickUp ticket
  • Account Manager notified; cleanup ticket closed

Common pitfalls (lessons from past cleanups)

  1. Hierarchical-parent trap. Children of a Draft parent stay published but disappear from the admin Pages list. Always cross-check the REST API. (Mootslaw, 2026-05-23 — 12 pages publicly indexed while invisible in admin)
  2. WP admin search matches title + content, not slug. Pages with slugs containing -old whose titles are clean ("Roland F. Moots, Jr.") will not appear in a title search for "old". Use the REST API or filter by Drafts directly.
  3. "Disabled" plugin still injecting headers. PHP opcache or partial hook registration can keep plugin output flowing after admin-level deactivation. Verify with curl, not the admin UI.
  4. Stale robots.txt sitemap pointer. If you switch sitemap plugins, the previous plugin's sitemap URL stays in robots.txt until manually updated. A Sitemap: line that 404s is worse than no Sitemap: line at all.
  5. REST API tells the truth when the admin UI hides things. /wp-json/wp/v2/pages?per_page=100 is the most reliable inventory query. If you see a discrepancy between WP admin counts and REST output, trust REST.
  6. 301 before delete, not after. Once a page is gone, you can no longer copy its URL/slug from the editor for the redirect rule. Set the redirect first, then trash the page.