Moving to Managed Hosting Without a Maintenance Window
A first move to managed WordPress hosting in the order it must happen: staging, a lowered TTL, serialised data, uploads bigger than the database, and forgotten mail.
Devon Parker · @dparker
APIs and integrations
- Topic
- INFRA
- Section
- Hosting & Infrastructure
- Published
- 17 OCT 2026
- Reading time
- 9 MIN
- Words
- 2092W
The maintenance window was booked for eleven at night and the cutover took twenty minutes. DNS changed at twenty past, the new server answered by half past, and the site never went down, because nobody took it down. That is the normal shape of a managed migration, and why the damage that follows is easy to miss.
The bill arrived nine days later. A product page had a broken gallery, because one nested folder inside wp-content/uploads had never copied, and those were the pages nobody opened. Then a customer said the password reset never arrived. It had not, because MX still pointed at the old host, and the mailbox had gone with the billing period.
A maintenance window does not reduce the risk of a migration. It moves the risk to a time when nobody is watching. The work belongs on a staging copy. The live site gets a DNS record and a cache purge.
Disclosure: some links on this page are affiliate links, and WPSifero may earn a commission if you buy through them. That does not change the price you pay, and it does not change what this piece recommends: the cheap alternative is named beside the managed plan, and a later section argues against buying.
What managed hosting changes about the job
A managed plan sells one thing: somebody else owns the server. For a migration it buys three things: a staging copy that exists, a certificate issued without a CSR, and a support queue that can restore a backup you forgot. Only the first removes the maintenance window.
What it takes away is the ability to run the destination by hand. Check three things before paying: the PHP extension the theme needs for images, whether SSH is included, and whether staging is reachable from the open internet.
A managed plan starts paying for itself when the staging copy is the reason a plugin update gets tested rather than discovered in production. Pressable's managed WordPress plans are the destination this runbook was written against, and the page to read first lists which tiers include staging and a refresh to production. Check the current price, they change it.
The free version is a second install on the same account: a subdomain, a copy of the database, and a rule that keeps it out of the index. Slower, deleted by hand, and it teaches you what the managed button does. Neither free route tests mail or the scheduler.
The staging copy is the migration
Two ways exist to see the new site before the domain points at it. The host's temporary address serves it while the database keeps the real domain; a hosts file entry sends the real domain to the new server for one machine. Prefer the second, which tests what visitors request, certificate included.
On the temporary address, the two lines that matter in wp-config.php are WP_HOME and WP_SITEURL. Leaving them in place after the cutover is the usual cause of an admin that redirects to staging.
Then break things on purpose: log in, publish, upload, check out, submit the form, reset a password, run wp cron event list. Each failure caught here will not happen in front of a customer.
The database is usually small: a decade of revisions rarely pushes a dump past a few hundred megabytes. Uploads are a different object. Years of photographs with every generated size beside them, PDFs, product images, cached plugin files, and often a backup archive a plugin left in the folder. Files commonly outweigh the database by an order of magnitude or two. Start that copy first.
rsync -avz --checksum --delete-after \
./wp-content/uploads/ \
newhost:~/public/wp-content/uploads/
# compare the two sides by count, not by size
find ./wp-content/uploads -type f | wc -l
ssh newhost 'find ~/public/wp-content/uploads -type f | wc -l'
Folder sizes lie twice: compression changes what travels, and hosts count disk usage differently. A file count is honest, and a second pass with rsync -n --itemize-changes shows what the first missed. It should finish in minutes. If it wants to move gigabytes again, permissions are being rewritten.
Failures cluster in four places. A nested folder a filter excluded, which is what bit the site at the top. Filenames differing only by case, where a laptop treats Logo.png and logo.png as one file and the server as two. Ownership: files copied as the wrong user serve reads, then fail the first upload. And a plugin that rewrote images in place.
The search-and-replace that eats serialised data
A replace is needed because WordPress stores absolute URLs in more places than post content: options, widgets, theme mods, plugin settings arrays. Many are serialised PHP, where a string carries its length, as in s:31:"https://old.example.com/file.png";. That number is part of the data.
Run UPDATE wp_options SET option_value = REPLACE(...) in phpMyAdmin and rows where the two domains happen to be the same length survive. The rest get a length that no longer matches, unserialize() fails, and WordPress falls back to a default silently. The widget vanishes, the colour resets, the form stops sending. Some rows survive, so the damage looks random.
wp search-replace 'https://old.example.com' 'https://new.example.com' \
--all-tables --precise --dry-run
# read the report, then run again without --dry-run
wp cache flush
wp rewrite flush
The search-replace command in WP-CLI unserialises and re-serialises, so lengths stay correct. --precise handles values nested inside other serialised values, and it is what to use when you get one attempt. The dry run prints every table and its replacement count, which is also the inventory of where the old domain was hiding. Leave guid alone: it is an identifier, not a link.
Do it on staging, before the cutover, and do not touch the database again. That order only works if staging already believes it lives on the real domain. On a temporary address the sequence needs one more replace, run between the DNS change and the cache purge.
Lower the TTL a day early, not an hour before
The TTL is the one part you cannot hurry. It is a promise to every resolver about how long they may keep the old answer, and it was made when the record was created. Setting the A record to a few minutes an hour before the cutover changes nothing for anyone who asked earlier.
So it is two steps a day apart. A day before, set the TTL on the A record, any AAAA record and the www CNAME to a few minutes, then wait out the previous TTL so the short one is what resolvers hold.
dig +noall +answer example.com A
example.com. 300 IN A 203.0.113.10
The forgotten record is AAAA. A domain whose IPv6 address still points at the old server serves the old site to every visitor on a network that prefers IPv6. That arrives as a report that DNS has not propagated, from somebody on mobile data. Update it or delete it, deliberately.
The mail nobody remembers until a password reset fails
Mail lives at the old host as a side effect of buying hosting, and it travels with neither the files nor the database. Mailboxes, forwarders, a catch-all, SPF, the DKIM selector, DMARC: the inventory is short and always incomplete. Copy the DKIM record rather than regenerating it if mail keeps flowing through the old provider, because the selector must match the key that signs the messages. Change MX last, after the mailboxes exist and a test has gone both ways.
The second half is outgoing mail from the site. Contact forms, order confirmations and password resets usually send through the server's local transfer agent, so they change identity on the night. The admin's own password reset is one of the messages that starts landing in spam.
The free alternative is to take mail off hosting entirely and run it at a mail provider, with the records in a zone you control. An evening of work, and the next hosting move has no mail in it.
- Inventory: the DNS zone to a text file, mailboxes and forwarders, hand-written cron entries, PHP extensions, plugins with their own tables.
- Back up files and database, then restore the archive somewhere that is not production. An untested backup is a wish.
- Lower the TTL on A, AAAA and the
wwwCNAME, and note the time. - Start the uploads copy and let it run while the rest of this happens.
- Create the database at the new host, import the dump, create the user WordPress needs.
- Point staging at it, with
WP_HOMEandWP_SITEURLset, or through a hosts file. - Run the search-and-replace dry, then for real. Flush the object cache and the rewrite rules.
- Test on staging: log in, upload, publish, check out, submit the form, reset a password, list cron events.
- Create mailboxes, publish SPF, DKIM and DMARC, send and receive a test both ways.
- Cut over: change A and AAAA, confirm the certificate, remove the temporary settings, purge every cache.
- Verify by address, then through the domain in a private window. Change MX only now, if mail moves too.
- Keep the old host paid and untouched for a week.
The first is anywhere before step ten. Every earlier step is additive: the old site serves, DNS still points at it, and cancelling means deleting the staging install and going to bed. That is why the runbook does not belong in one evening. If staging has a 500 nobody can explain, fix it and pick another night.
The second is after the DNS change, while the TTL is short and nothing has been written to the new site that the old one lacks. Point the A record back and the world returns within the minutes the TTL allows. Data closes that window rather than the clock: the first order, the first comment, the first post published on the new site. After that, pointing back is a second migration with divergent content. So on the night, do not schedule posts or start a sale.
The week after: error log first, cache second
Read the error log before the traffic graph, because the failures that matter are quiet. 404s under wp-content/uploads are the files the copy missed. Missing PHP extension warnings break image handling without stopping the page. Failures in a request the site makes to itself matter most: a host that blocks loopback puts the scheduler to sleep, and scheduled posts never publish.
Then there is the cache that makes people think the migration failed. The host's page cache, the CDN and the visitor's browser can each hold HTML generated before the move, including pages carrying the old asset paths or the temporary domain in a form action. The symptoms are the old theme, a login that redirects to staging, and a price one edit out of date.
curl -sI --resolve example.com:443:203.0.113.10 https://example.com/ \
| grep -i -E 'x-cache|age|via'
If that request is correct and the browser is wrong, the migration worked and the cache has not caught up. Purge in order: host page cache, persistent object cache, CDN, then a private window. Flushing the object cache at cutover matters anyway, because a persistent cache carried across in the dump holds keys pointing at the old server.
When managed hosting is the wrong purchase
A brochure site with no revenue, standard plugins and a few hundred visitors a day does not need a managed plan. A cheap account with a staging plugin performs the same migration, and the runbook above applies unchanged. A site that has never used staging is paying for a button.
A site with a nonstandard requirement does worse on a platform that offers upload, staging and a purge button and stops there. A custom nginx rule, a specific PHP extension, a persistent socket, or a mail setup the panel does not expose each becomes a conversation with support. Those sites want a server they own.
The timing argument is real too. A store in the middle of a season should not migrate, because the second rollback point expires at the first order, and an order is what a shop is for.
The decision rule, then. If the site is quiet and the only staging you will ever do is on the night, do not pay for staging, and skip the window too, since the hour was never the risky part. If a staged copy is how you test plugin updates from now on, buy a plan that lists staging per tier, and spend the first refresh on this migration. Either way, lower the TTL first: it is the step that must happen before the night.
Devon Parker · @dparker
APIs and integrations
Works through REST contract design, webhook reliability, idempotency keys, retry policy and the unglamorous edge cases that appear when two systems disagree about what happened.
Continue