Every hosting comparison I've ever read — including ones I've written — is organized around the cost of arriving. Monthly price, included features, what the entry tier gets you.
That's the number the vendor chose to publish. It's also the number that stops mattering about ninety days in.
The number that actually determines whether a platform decision was good is the one nobody publishes: what it costs to leave. Not in dollars — in coupling. How much of your stack lives somewhere you can't export, how much of your deploy process assumes this specific vendor, and how much of "moving" is really "rebuilding."
I started running an exit audit before committing to a platform rather than after regretting one, and it changed which options I take seriously. Here's the framework and the scripts.
Why exit cost is the better selection criterion
Entry cost is a known quantity you pay once a month. Exit cost is an unknown quantity you pay under duress — during a renewal spike, after an incident, when a client demands a move, or when the platform changes its pricing model.
More usefully: exit cost is a proxy for how much of your infrastructure you actually understand. A stack you can move is a stack you can explain. One you can't move usually has undocumented state somewhere, and that's a problem independent of hosting.
The asymmetry that makes this worth formalizing:
- Entry decisions are made with full attention, at leisure, with vendor documentation in front of you.
- Exit decisions are made with partial attention, under time pressure, with the documentation you didn't write.
So you want the exit path designed at entry time, when you're the version of yourself with time to think.
Decomposing exit cost
"Lock-in" is too coarse to act on. In practice, hosting exit cost breaks into five components, and platforms score very differently across them.
1. Data portability. Is your data in standard, dumpable places — a MySQL database and a filesystem — or partly in vendor-managed stores you can only reach through a panel? For WordPress this is mostly good news: the database plus wp-content is a genuinely portable unit. The exceptions matter, though, and they're covered below.
2. Configuration coupling. How much of your working setup exists only as clicks in a panel? Nginx rules, redirects, PHP settings, cron definitions, firewall rules, and cache behavior are all things that can live either in files you control or in a UI you can't export. This is the component people underestimate most.
3. Runtime coupling. Drop-ins and must-use plugins that only work on this host. object-cache.php tied to a specific Redis setup. advanced-cache.php from a host-specific caching plugin. Edge rules configured in the vendor's dashboard. These don't come across, and the site behaves differently without them — often subtly, which is worse than loudly.
4. Contract coupling. Annual prepayment, promotional terms that reset on a specific date, and multi-year commitments. Not technical, but it sets when you're allowed to leave without eating cost. A renewal date is a scheduling constraint on your architecture.
5. Rebuild surface. How many things you'd have to recreate by hand rather than restore. This is where TLS, DNS, mail routing, webhooks, and background jobs live — and it's the part that's fine in the dry run and painful in production, because half of it is external systems pointing at you.
Audit your current host before you shop
You can't compare exit costs meaningfully until you know what your current one is. This inventories the coupling on a running WordPress host. It's read-only.
#!/usr/bin/env bash
# exit-audit.sh — inventory what would NOT come across in a migration.
# Read-only. Run on the current host, from the WordPress root.
set -uo pipefail
echo "=== drop-ins (host-specific by nature) ==="
# object-cache.php and advanced-cache.php are installed BY the host or by a
# host-specific plugin. They will not work elsewhere and are the most common
# cause of "the site is up but everything is slow" after a move.
ls -la wp-content/{object-cache,advanced-cache,db,maintenance}.php 2>/dev/null \
|| echo " none"
echo "=== must-use plugins (auto-loaded, often vendor-injected) ==="
ls -la wp-content/mu-plugins/ 2>/dev/null || echo " none"
echo "=== host-specific constants in wp-config.php ==="
# Anything referencing a vendor name, a Redis socket path, or an absolute
# path outside the webroot is configuration you will have to recreate.
grep -nE "define\(.*(REDIS|MEMCACHED|CACHE|CDN|WP_ENVIRONMENT|_HOST)" wp-config.php
echo "=== where cron actually runs ==="
grep -n "DISABLE_WP_CRON" wp-config.php || echo " WP-Cron on page loads (portable)"
crontab -l 2>/dev/null | grep -i "wp\|php" || echo " no user crontab visible"
echo "=== transfer size ==="
du -sh wp-content/uploads 2>/dev/null
wp db size --human-readable 2>/dev/null || echo " (wp-cli not available)"
echo "=== plugins whose names imply host coupling ==="
wp plugin list --field=name 2>/dev/null \
| grep -iE "kinsta|wpengine|wp-engine|cloudways|siteground|breeze|sg-|rocket-net|hostinger" \
|| echo " none detected"
The output is your exit cost, itemized. A host where that script prints almost nothing is a host you can leave in an afternoon. A host where it prints two vendor mu-plugins, a proprietary caching drop-in, and cron defined in a panel you can't grep is a host where "moving" means "rebuilding and hoping."
Neither result is automatically bad. But you should know which one you're buying.
What actually has to travel
The second thing worth doing before you shop is proving your export is complete. Most people export the database and wp-content and assume that's everything. It usually isn't — the gaps are in the runtime, not the content.
#!/usr/bin/env bash
# portable-export.sh — an export that restores somewhere else,
# including the environment metadata people forget to record.
set -euo pipefail
OUT="export-$(date +%F)"
mkdir -p "$OUT"
# 1. Database. --single-transaction avoids locking a live site;
# --default-character-set prevents the mojibake that shows up
# three weeks later in one old post nobody reads until a client does.
wp db export "$OUT/db.sql" \
--single-transaction \
--default-character-set=utf8mb4
# 2. Content. Excluding cache dirs keeps the archive honest —
# a restored cache from another host is worse than an empty one.
tar czf "$OUT/wp-content.tgz" \
--exclude='wp-content/cache' \
--exclude='wp-content/uploads/cache' \
--exclude='wp-content/object-cache.php' \
--exclude='wp-content/advanced-cache.php' \
wp-content
# 3. The manifest. This is the part that gets skipped, and it's the part
# that tells you why the site behaves differently on the new host.
{
echo "php_version: $(php -r 'echo PHP_VERSION;')"
echo "php_extensions: $(php -m | tr '\n' ',' )"
echo "mysql_version: $(wp db query 'SELECT VERSION()' --skip-column-names 2>/dev/null)"
echo "wp_version: $(wp core version)"
echo "site_url: $(wp option get siteurl)"
echo "home_url: $(wp option get home)"
echo "active_theme: $(wp theme list --status=active --field=name)"
echo "php_memory_limit: $(php -r 'echo ini_get("memory_limit");')"
echo "max_execution_time: $(php -r 'echo ini_get("max_execution_time");')"
} > "$OUT/manifest.txt"
# 4. Anything the site sends or receives. These point at you from outside,
# so they don't move when you do — list them now, fix them at cutover.
wp option get siteurl > "$OUT/urls.txt"
wp cron event list --format=csv > "$OUT/cron-events.csv" 2>/dev/null || true
echo "exported to $OUT/"
The manifest is the highest-value part of that script and takes the least effort. PHP version and extension mismatches produce the class of bug where the site loads, most things work, and one plugin silently fails — the kind you find via a customer email rather than an error page.
And the URL rewrite is a genuine footgun worth naming, because WordPress stores serialized arrays in the database and a naive find-and-replace corrupts them:
# Always dry-run first. --precise handles serialized data correctly;
# a plain sed over the SQL dump does not, and the damage is silent.
wp search-replace 'https://old.example.com' 'https://new.example.com' \
--precise --recurse-objects --all-tables --dry-run
# --skip-columns=guid because GUIDs are identifiers, not links.
# Rewriting them breaks feed readers' de-duplication.
wp search-replace 'https://old.example.com' 'https://new.example.com' \
--precise --recurse-objects --all-tables --skip-columns=guid
Reversibility by platform shape
Now the comparison is tractable — not "which is best," but "what does each shape couple you to, and what exits cleanly." Prices are August 2026 marketing anchors; verify on the vendor's page before buying.
A management layer over IaaS you choose. Cloudways Flexible provisions onto DigitalOcean, Vultr, Linode, AWS, or GCP; entry DigitalOcean-class sizing was marketed near $11/mo. Couples you to: the panel's configuration — server settings, cron entries, and staging workflows defined in a UI rather than in your repo. Its caching plugin is a drop-in that doesn't travel. Exits cleanly: the data, obviously, but also the mental model — you were already thinking in terms of a sized VM, so the destination is conceptually similar. Structural note: since multiple apps commonly share one server here, "leaving" can mean unpicking several sites from one box rather than moving one thing.
Per-site premium managed WordPress. Kinsta (marketing often ~$35/mo single site, with multi-site tiers around $115 / $225 / $340 for 5 / 10 / 20), WP Engine (Essential Startup ~$30 first-year class, priced on visits), and Rocket.net (Starter ~$30 monthly / ~$25 annual) sell WordPress operations as the product. Couples you to: vendor mu-plugins, host-specific caching, and in some cases edge configuration held in the dashboard. Your deploy and staging workflow becomes theirs. Exits cleanly: the database and uploads, and these platforms generally have competent export tooling — the friction is runtime, not data. Structural note: per-site pricing means exit cost scales with site count too. Ten sites is ten migrations, not one.
Vendor-panel managed VPS. ScalaHosting markets managed VPS with its own SPanel (managed commonly ~$29.95 intro → ~$54.95 renew; unmanaged around ~$19.95 → ~$24.95). Couples you to: the panel, which is the product. Whatever you configure there is configured there. Exits cleanly: it's still a VM with a normal filesystem underneath, so data portability is good. Structural note: you're buying one vendor's box plus their panel, not provider choice under a panel — worth being explicit about, because it's a different product from the multi-cloud management-layer shape above even though both say "managed VPS."
Promotional shared and simple cloud. SiteGround (StartUp around $2.99 intro against a materially higher renewal, commonly quoted in the $14.99–$24.99 range by term), Hostinger (Premium shared ~$2.99 → ~$10.99; Cloud Startup ~$7.99 → ~$25.99; KVM 2 ~$8.79 → ~$14.99), and InMotion (shared ~$4.79 → ~$14; VPS 4 ~$9.99 → ~$16.99). Couples you to: the contract more than the technology. Annual prepay plus a renewal step function means the cheap exit window is narrow and dated. Exits cleanly: usually the technical side is straightforward — cPanel-style environments are conventional. Structural note: the exit constraint here is a calendar entry, not an engineering problem. Know your renewal date and treat it as the decision point.
Raw IaaS. Vultr (Regular ~$2.50–$3.50), DigitalOcean (Basic from ~$4), Linode (from ~$5), Kamatera (from ~$4), Contabo (from ~€5.50), RackNerd (specials often ~$22–$120/year). Couples you to: essentially nothing at the platform level — which is the honest appeal. Exits cleanly: completely, if your provisioning is in code. If it isn't, you've simply moved the lock-in from a vendor to an undocumented server, which is worse: at least a vendor has support. Structural note: these replace the VM, not a management layer. If you're comparing them against a managed product, you're comparing different things — the managed vs unmanaged breakdown covers that distinction properly.
Summarized:
| Platform shape | Heaviest coupling | Exits cleanly | Exit is gated by |
|---|---|---|---|
| Management layer over IaaS | Panel-held config, cache drop-in | Data; VM-shaped mental model | Unpicking co-located apps |
| Per-site premium managed WP | mu-plugins, edge rules, workflow | Data; decent export tooling | Site count — each is a migration |
| Vendor-panel managed VPS | The panel itself | Filesystem and DB | Recreating panel config by hand |
| Promotional shared / simple cloud | The contract | Conventional environments | Renewal date |
| Raw IaaS | Nothing — or your own undocumented box | Everything, if provisioning is code | Whether you wrote it down |
The coupling worth accepting
A framework that always concludes "minimize lock-in" isn't a framework, it's a reflex. Some coupling is a good trade.
Accept coupling that removes work you'd otherwise do badly. A host-specific caching drop-in that's correctly configured beats a portable one you'll never tune. If the coupled version means your object cache actually works, that's a real gain against a hypothetical future migration.
Accept coupling when the exit is naturally scheduled. A one-year contract on a site you'll rebuild next year costs nothing. Lock-in only bites when it collides with a need you didn't anticipate.
Don't accept coupling you can't inventory. This is the line I'd hold. Configuration living somewhere you can't list, diff, or version isn't lock-in you chose — it's lock-in you'll discover. That's the difference between a trade and a surprise, and the audit script above exists to keep it on the right side of that line.
Migration order that avoids double-paying
If the audit sends you elsewhere, sequence matters more than speed:
- Match the category before the vendor. Moving from a per-server management layer to per-site managed WordPress is a different product, not a cheaper version of the same one. Category mismatches are the migrations people regret.
- Sort out mail first if either side bundles it. Discovering mid-cutover that email lived on the old host is the single most avoidable migration emergency.
- Restore into staging on the destination and diff against the manifest — PHP version, extensions, memory limit, execution time. Fix mismatches before DNS moves, not after.
- Lower DNS TTL a day ahead, then cut over.
- TLS, caching, and cron in that order after cutover. Verify cron is actually firing on the new host rather than assuming it inherited.
- Test the things external systems touch — payment webhooks, form handlers, anything with a callback URL registered elsewhere. These don't move with you and fail silently.
- Cancel the old plan only after a full billing-cycle's worth of confidence, and check whether you've already prepaid a term.
FAQ
How do I know if I'm locked into my hosting provider?
Inventory what wouldn't survive a move: drop-in files like object-cache.php and advanced-cache.php, vendor mu-plugins, cron defined in a panel rather than a crontab, and any configuration that exists only as UI state. If you can't produce that list, that uncertainty is the lock-in.
Is WordPress hosting portable between providers?
The data is — the database plus wp-content moves cleanly. The runtime often doesn't: caching drop-ins, host-specific plugins, PHP version and extension differences, and cron arrangements are recreated rather than restored.
What's the most commonly forgotten thing in a hosting migration?
Environment metadata and external callbacks. PHP version and extension mismatches cause silent partial failures, and webhooks registered in third-party dashboards keep pointing at the old host until you update them there.
Does moving to unmanaged hosting eliminate lock-in?
Only if provisioning is in version control. Otherwise you've relocated the coupling from a vendor with documentation and support to a server that has neither.
When is vendor lock-in an acceptable trade?
When it removes operational work you wouldn't otherwise do well, and when you can still inventory exactly what you're coupled to. Coupling you can list is a decision; coupling you can't is a liability.
Should exit cost outrank monthly price when choosing a host?
For anything you expect to run for years, yes — monthly price differences are small and knowable, while exit cost is large and only discovered under pressure. For a short-lived project, entry price is the reasonable criterion.
The framework fits in one question, asked before you sign rather than after: if I had to leave this in six months, what exactly would I have to rebuild by hand?
If you can answer precisely, any platform on the list is a defensible choice, because you've priced the real risk. If you can't answer at all, that's not a hosting problem — and it'll follow you to the next provider.
No affiliate links in this post — every link points to my own research pages. Prices quoted are August 2026 marketing anchors and will change; verify on the vendor's site before purchasing.
Top comments (0)