We Forgot to Drain the Queue Before the VPS Swap
A field note from a 48-hour migration where 300 emails got stuck in the Exim queue after a VPS swap, and how we rebuilt the cutover playbook.
Author
The Incident: Mail Stuck in the Exim Queue After a VPS Swap
Last month we migrated a WooCommerce client off a budget VPS that was being deprecated by the provider. The migration window was 48 hours, there was no staging environment, and the site ran on a mix of WordPress, an Exim mail server, and a handful of custom cron jobs. We had rehearsed the file copy, the database dump, and the DNS switch. What we did not rehearse was the mail queue.
We started with a live rsync of /home and /var/lib/mysql while services were still running. We assumed the mail would flush naturally once DNS propagated. Twelve hours later, exim -bp on the old host showed 300+ messages stuck in the queue. No outbound delivery. No bounce notifications. Just a silent pile of order confirmations and password resets that customers were not receiving.
That is when we remembered: we had never drained the queue before stopping Exim on the old host.
The Working Approach: Controlled Cutover With Queue Draining
Step 1: Pre-Migration Inventory and Access Check
Before touching anything, we catalogued every moving part: websites, databases, mailboxes, forwarders, cron jobs, SSL certs, and custom DNS records. We confirmed admin credentials for the registrar, the DNS host, and the source VPS backup/export capability. We verified SSH key access and sudo privileges on both the old and new VPS.
This step is boring until it is not. Halfway through the inventory we found a cron job that wrote to a custom directory outside /home. If we had missed it, the new host would have silently failed to process orders.
Step 2: Lower DNS TTL 24-48 Hours Ahead
We set the A, AAAA, MX, and www records to 300 seconds using the registrar web UI. We confirmed the TTL changes propagated before proceeding. This is the single biggest lever nobody uses, but it cuts propagation time from hours to minutes on cutover day.
Step 3: Provision and Harden the New VPS
We installed a LEMP stack on Ubuntu 22.04. We enabled OPcache, HTTP/2, GZIP/Brotli, and Redis object cache. We configured fail2ban, UFW firewall, and SSH key-only authentication. We tuned MariaDB buffer sizes and added swap because the new plan had only 2 GB of RAM.
Step 4: Copy Data and Test on Staging
We created full site and database backups stored offsite. We used rsync -avz for files and mysqldump for databases. We tested the site via a hosts file override on our local device. We ran wp search-replace with --dry-run first to fix hardcoded URLs.
Step 5: Drain the Mail Queue Before Cutover
This is the step we skipped the first time. On the old host we ran:
sudo systemctl stop exim4
sudo exim -bp
sudo exim -q -i
sudo exim -bp | wc -l
We monitored the queue until it was empty. If stuck messages persisted, we manually flushed or rerouted them via a temporary SMTP relay.
Step 6: Freeze Writes and Take Final Sync
We put the site in maintenance mode and temporarily blocked write actions like checkout and posting. We took a final database dump:
mysqldump -u root -p database_name > final_dump.sql
We synced the uploads directory:
rsync -avz /var/www/html/wp-content/uploads/ newhost:/var/www/html/wp-content/uploads/
Step 7: Switch DNS and Monitor
We updated the A record to point to the new VPS IP. We kept the old host online for 48-72 hours to catch stragglers during propagation. We monitored DNS propagation from 10 global resolvers. We verified SSL status, cron jobs, email routing, and the queue on the new host.
Step 8: Raise TTL and Decommission Old Host
We restored the TTL to 3600 seconds after stability was confirmed. We kept the old host snapshot for 30 days for rollback safety. We removed the temporary SMTP relay if we had used one.
Pitfalls We Would Warn an Intern About
Never assume mail will flush on its own after DNS changes. Always drain queues (Exim, Postfix, etc.) before stopping services on the old host. Do not skip the hosts file preview test; hardcoded URLs break silently. Watch for version mismatches (PHP, MySQL) that break legacy code. Fix file ownership rather than loosening permissions to resolve 500 errors. Do not leave password SSH logins enabled; always use key-based auth. Missing SPF/DKIM updates cause emails to land in spam or get rejected. SSL must be reissued on the new host using DNS-01 challenge if the site is not yet live.
What We Would Do Differently Next Time
We would schedule the cutover during the lowest traffic window, which for this client was 1-5 AM IST. We would perform a dry-run migration to a throwaway subdomain on the new server. We would automate queue draining with a pre-cutover script that checks and flushes all queues. We would set up a temporary SMTP relay proactively instead of waiting for mail to get stuck. We would use host-level snapshots or image exports before changing DNS, even if reinstall is faster. We would validate virtio/storage drivers ahead of downtime to avoid silent regressions. We would plan the IP cutover consciously: SMTP reputation, webhook callbacks, and VPN allow-lists all need updating. We would keep rollback steps documented in ticket updates, not frantic WhatsApp voice notes.
The migration eventually succeeded, but only after we manually replayed 300 emails through a temporary relay and spent an extra day cleaning up customer support tickets. The playbook above is what we now walk through with every startup moving off a random VPS.
Sources:
- VPS Hosting Migration: Cut Downtime and Avoid Surprises
- How to Migrate from Shared Hosting to VPS Hosting in India
- How we do zero-downtime migrations (at 2am, while you sleep)
- VPS migration checklist: move a website with near-zero downtime (2026)
- Website migration checklist
- VPS Migration Checklist: DNS, Cutover & Rollback
Sources
Related reading
Enjoyed this article?
Back to Blog


