Migrate an App to a New Server with Minimal Downtime
This guide walks you through moving a live application to a new server while keeping downtime to seconds or minutes, not hours. You’ll prepare the new environment, sync data and code, then switch traffic with a controlled cutover. It assumes you have SSH access to both servers and can edit DNS or reverse proxy settings.
What You’ll Need
- SSH access to both the old and new servers (root or sudo privileges)
- A DNS provider or load balancer where you can change A records or routing rules
- Access to any external services your app depends on (database, cache, object storage) so you can update connection strings or IP allowlists
- A recent backup of your application data, even if you plan to sync live
- A rollback plan: know how to point traffic back to the old server if needed
Step-by-Step
- Inventory the application. List every component: web server, application code, database, cron jobs, background workers, environment variables, and any files stored locally (uploads, logs). Note the exact versions of software (PHP, Node, Python, MySQL, etc.) and any system packages.
- Provision the new server. Install the operating system and all required software with the same versions as the old server. Configure system services (firewall, SSH, timezone) and create the same user accounts and file permissions.
- Set up a staging copy. Deploy the application code to the new server and point it to a temporary database or a read-only copy. This lets you verify that the code runs on the new environment before going live.
- Sync static files and code. Use
rsyncto copy application code, uploads, and any other files from the old server to the new one. Run it with--archive --deleteto mirror changes. Example:rsync -avz --delete /var/www/app/ user@newserver:/var/www/app/. - Sync the database. For a live database, use a replication method if possible (e.g., MySQL replication, PostgreSQL streaming replication). If not, take a consistent dump during a low-traffic window and restore it on the new server. Keep the new database in sync by applying incremental changes or re-running the dump just before cutover.
- Test the new environment. Run the application on the new server against the synced data. Check all critical flows: login, data display, file uploads, scheduled tasks. Verify that any external API calls work (update IP allowlists if necessary).
- Prepare the cutover. Decide on a cutover window (usually late night or weekend). Write down the exact commands you’ll run. Have a rollback plan ready: e.g., keep the old server running and be prepared to switch DNS back.
- Freeze writes (optional but recommended). For a true minimal-downtime cutover, put the application into read-only mode or stop accepting new data for a few minutes. This ensures the final data sync is clean. If you can’t freeze, use replication to keep both databases in sync until the last moment.
- Final sync. Run the last
rsyncfor files and the last database dump (or promote the replica). This step should be quick if you’ve been syncing continuously. - Switch traffic. Change the DNS A record to point to the new server’s IP, or update your load balancer’s target group. If you use a reverse proxy (like Nginx) on the old server, you can also proxy traffic to the new server temporarily to reduce DNS propagation issues.
- Monitor and verify. After the switch, check that the site loads, database connections work, and no errors appear in logs. Keep an eye on server resources (CPU, memory, disk) for the first hour.
- Update internal references. Change any hardcoded IPs, cron job targets, or configuration files that still point to the old server. Update monitoring and backup scripts.
- Decommission the old server (after a grace period). Wait at least 24–48 hours to ensure no lingering traffic or missed dependencies. Then shut it down and take a final backup.
Common Mistakes
- Forgetting to sync cron jobs or background workers. These often live outside the main app and are easy to miss.
- Not updating IP allowlists. If your database or API only allows certain IPs, the new server will be blocked.
- Skipping the final sync. If you sync hours before cutover and then switch, you’ll lose any data changed in between.
- Changing DNS and expecting instant propagation. DNS can take hours to fully propagate, so plan for a transition period.
- Not testing the new server before going live. A missing PHP extension or wrong file permission can cause a long outage.
- Assuming file permissions are the same. Different OS versions or user setups can break uploads or session writes.
Tips
- Use a staging subdomain (e.g.,
staging.yourdomain.com) to test the new server before the cutover. - Automate the sync with a script that you can run repeatedly. This reduces human error during the final steps.
- Keep a terminal window open with a continuous ping to your domain to see exactly when the switch happens.
- If you use a load balancer, you can gradually shift traffic (e.g., 10% to the new server) to catch issues early.
- Document everything: IP addresses, commands, and rollback steps. This is invaluable if something goes wrong.
- Consider using a configuration management tool (like Ansible) to provision the new server identically.
Troubleshooting
- Site is down after DNS switch. Check that the new server’s firewall allows HTTP/HTTPS traffic. Verify that the web server is running and listening on the correct port. Check DNS propagation with a tool like
dig. - Database connection errors. Confirm the database service is running on the new server. Check the connection string in your app’s configuration. Ensure the database user has permissions from the new server’s IP.
- File uploads fail. Check directory permissions and ownership. The web server user (e.g.,
www-data) must have write access to the upload directory. - Some users still see the old site. That’s normal due to DNS caching. Wait for propagation or reduce the TTL before the cutover. You can also use a reverse proxy on the old server to forward traffic to the new one.
- Application errors after cutover. Check the application logs (e.g.,
/var/log/nginx/error.logorlaravel.log). Common issues: missing environment variables, wrong file paths, or missing PHP extensions. - Data inconsistency between old and new. If you didn’t freeze writes, some data may have been written to the old server after the last sync. In that case, you may need to re-sync and switch again, or accept a small loss if the data is not critical.
Frequently Asked Questions
How long will the downtime actually be?
With proper preparation, the downtime is usually the time it takes to switch DNS or update the load balancer — often under a minute. The final sync can be done in seconds if you’ve been syncing continuously.
Can I do this without any downtime?
True zero downtime is difficult with a single server and DNS. Using a load balancer with multiple servers can achieve zero downtime, but for a simple migration, a few seconds of downtime is typical and acceptable.
What if I can’t stop writes to the database?
Use database replication. Set up the new database as a replica of the old one, let it catch up, then promote it to primary during the cutover. This minimizes data loss.
Should I migrate the database first or the code?
It depends on your setup. Usually, you sync the database first, then the code, because the code might have schema changes. But if you use replication, you can sync both simultaneously.
How do I handle SSL certificates?
If you use Let’s Encrypt, you can issue a new certificate on the new server before the cutover. If you have a wildcard or commercial certificate, you’ll need to install it on the new server and ensure the private key matches.
What’s the safest rollback strategy?
Keep the old server running and intact until you’re confident the new server is stable. If something goes wrong, switch DNS back to the old server’s IP. You may lose any data written to the new server after the cutover, so that’s a risk to weigh.
Related
Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.