Removing WordPress Safely From a Host Running App APIs

21 leftover WordPress items removed from a shared host that also serves app backends: the pre-checks that matter, a reversible move-then-delete method, and .htaccess rules that answer 410 Gone.

This is for anyone who moved a blog off WordPress but still has its files sitting on a shared host that also runs other things, in our case the backends of several mobile apps. Our conclusion: once you cannot roll back anymore, delete the WordPress files. Do it from a hard-coded list, move each item outside the web root first, and only delete for good after the regression checks pass. Then answer the old system paths with 410 Gone, and keep the 301s for content that has a new home.

The blog moved from WordPress to a Python static site generator on 2026-11-08 (how we kept the app store URLs alive). The WordPress front controller was cut out of .htaccess, but its files stayed on the server so we could roll back. On 2026-11-09 the WordPress database was deleted. From that point the files could not run a site anymore. They were dead weight and attack surface, including a wp-config.php with real database credentials.

Why "just delete wp-*" is dangerous here

The same document root serves more than the blog. Among other things, there is an ad-configuration API that 16 installed apps call when they start. If a mistake makes PHP fail across the domain, the blog is the smallest problem. So before touching anything, we checked four things.

1. Do any apps call WordPress endpoints? We grepped about 20 Flutter app source trees for wp-content, wp-json, xmlrpc and wp-admin. There were 0 hits. Apps that once loaded images from wp-content/uploads or used the REST API would have been a reason to keep certain paths.

2. Is there an auto_prepend_file? This is the trap that is easy to forget. Security and WAF plugins for WordPress often add a .user.ini (or php.ini) with a line like this:

auto_prepend_file = '/home/…/public_html/wp-content/…/firewall-bootstrap.php'

PHP then runs that file before every PHP request in the directory tree, not only WordPress requests. Delete wp-content and every PHP script on the domain fails with a fatal "Failed opening required" error, including the app APIs. We checked for .user.ini and php.ini files. None existed, so this risk did not apply to us. If one exists on your server, remove the setting and wait for the .user.ini cache to expire (often five minutes) before removing the file it points to.

3. Is the root index.php really WordPress? Any non-WordPress PHP on a shared host might be called index.php. We downloaded it and checked that it contained WP_USE_THEMES, the line that boots the WordPress theme loader. The removal script refuses to run if that check fails. The static home page kept working because DirectoryIndex index.html index.php already served index.html first.

4. Is there anything in the backup plugin's folder? The migration plugin had a backup directory inside wp-content. It held only placeholder files and no actual archives, so there was nothing to keep.

What was left over

Besides the obvious wp-admin, wp-includes, wp-content and the wp-*.php files:

Item Why it matters
wp-config.php Database host, user and password in plain text
xmlrpc.php A classic target for credential-stuffing and pingback abuse
license.txt, readme.html Reveal the WordPress version to scanners
A host-generated one-click auto-login PHP file Created by the hosting panel's "log in to WordPress" button; not part of WordPress
Object-cache and LiteSpeed config inside wp-content Plugin configuration that no longer applies

In total: 21 items, 3 directories and 18 files. The auto-login helper is worth a special look. It does not come with WordPress, so checklists made for WordPress do not mention it. Check your document root for files your host's panel created.

Method: move, verify, then delete

The removal script had three properties:

  1. An explicit, hard-coded list of names. No glob, no wp-* pattern. A pattern delete on a shared root is how you lose a folder called wp-backup-of-the-app-api that someone created years ago.
  2. A refuse-list. The script asserted that none of the listed names were in a set of protected names: the API folders, the app folders, the language folders, index.html, .htaccess, ads.txt and app-ads.txt. That only checks the list itself, but it catches the kind of editing mistake that matters.
  3. Rename, not delete. Each item was moved by FTP RENAME into an archive folder outside the web root. A rename is instant even for a directory with thousands of files, it takes the item off the web immediately, and it can be undone with another rename.
DIRS  = ["wp-admin", "wp-content", "wp-includes"]
FILES = ["index.php", "wp-config.php", "xmlrpc.php", "license.txt", "readme.html", …]
NEVER = {"api", "app", "en", "ja", "ko", "static", "index.html", ".htaccess", "ads.txt", "app-ads.txt", …}

assert not (set(DIRS + FILES) & NEVER)
if b"WP_USE_THEMES" not in root_index_php:
    sys.exit("abort: root index.php is not WordPress")
for name in targets:
    ftp.rename(f"{base}/{name}", f"{ARCHIVE}/{name}")

After the move we ran the full regression checks: the site URL list and the API response comparison. Only after both passed did we delete the archive. That delete ran in the background, walking the tree with MLSD and removing files one by one. Over FTP this takes a long time for wp-includes, which is why it should not happen while the site is in an unknown state.

.htaccess: 410 for system paths, 301 for content

With the files gone, WordPress paths would fall through to a 404. We made them explicit, right after the existing security block:

# WordPress was removed on purpose: answer 410 Gone
RewriteRule ^(wp-admin|wp-includes|wp-content|wp-json)(/|$) - [G,L]
RewriteRule ^(wp-[a-z-]+|xmlrpc)\.php$ - [G,L]

A few details:

  • (/|$) stops the first rule from matching longer names that start the same way. A request for /wpx-test/ or a hypothetical /wp-contentful/ is not caught.
  • The second rule only matches .php. /wp-sitemap.xml therefore still reaches its older rule and gets a 301 to /sitemap.xml, which search engines may have stored.
  • Old post URLs, category archives, /feed/ and /?p=ID keep their 301s to the new pages. They are content with a new address, not "gone".
  • Old image URLs under wp-content/uploads/ now return 410. If you have migrated images with backlinks you care about, put 301 rules for them before this block.

We did not deploy this directly. We generated the full .htaccess, put it into an isolated sandbox subfolder with RewriteBase rewritten to that folder, and ran 19 test cases against it: 19/19 passed. Among them were /wpx-test/ still returning 404, a POST to /xmlrpc.php returning 410, and an old uploads image path returning 410. After the real deploy we saw the same status codes, 53/53 site URL checks passed, and the API regression showed 0 failures.

Old .htaccess backups remain on the server for reference. They are not readable from the web because of a rule that was already there:

RewriteRule (^|/)\.htaccess(\.|_)[^/]*$ - [F,L]

404 or 410?

Google's documentation treats both as "this page does not exist" and drops such URLs from the index. 410 is the more explicit signal: the server says the resource was removed on purpose and is not coming back. Some sources say 410 leads to slightly faster removal. We did not measure that and do not claim it.

We chose 410 for a different reason: to keep our own logs readable. A 404 on /wp-login.php could mean a broken link, a typo or a scanner. A 410 says "this is the WordPress path we removed deliberately". When a 404 shows up in the logs, it is now worth looking at.

URL type Response
Old post, category, feed, /?p=ID 301 to the new page
/wp-sitemap.xml 301 to /sitemap.xml
wp-admin/, wp-includes/, wp-content/, wp-json/ 410
wp-*.php, xmlrpc.php (any method) 410
Anything else that does not exist 404

Checklist

  1. Make sure rollback is truly gone (the database is deleted), or keep the files until it is.
  2. Grep your app sources for wp-content, wp-json, xmlrpc, wp-admin.
  3. Look for auto_prepend_file in any .user.ini or php.ini.
  4. Check that the root index.php contains WP_USE_THEMES and that DirectoryIndex serves your static home first.
  5. List the items by hand, including host-generated helpers, and check the list against a set of protected names.
  6. Rename everything outside the web root, run regression checks, and only then delete.
  7. Test the new .htaccess in a sandbox folder, then deploy, then re-run the same cases live.

FAQ

Can deleting wp-content break PHP that has nothing to do with WordPress?

Yes, if a .user.ini or php.ini sets auto_prepend_file to a file inside wp-content. Security and firewall plugins often install one. Once the file is gone, every PHP request on the domain fails, including unrelated APIs. Check for it before deleting anything.

Which WordPress files are worth removing even after the site is gone?

wp-config.php (database credentials), xmlrpc.php, license.txt and readme.html (they reveal the version), any host-generated auto-login helper, and the wp-admin, wp-includes and wp-content folders. We removed 21 items in total.

Should removed WordPress URLs return 404 or 410?

Google documents that both lead to removal from the index. 410 Gone is the more explicit signal that the removal was intentional. We used 410 for WordPress system paths and kept 301 redirects for old posts and feeds that have a new home.

  • #wordpress
  • #htaccess
  • #410-gone
  • #security
  • #shared-hosting
  • #ftp
  • #litespeed

t.laboratory

A technical blog that records real problems from the apps, servers and services we build and run — and the code that fixed them.

About · Apps

↑