Moving from shared hosting to a VPS sounds scarier than it is. The site goes down during migration, the database doesn't import cleanly, the PHP version is different — these are the stories people tell. But almost all of them come from the same source: skipping steps and rushing the cutover.
Follow this checklist in order and your migration will be predictable. There are no shortcuts worth taking here.
When you actually need to migrate
Before you start: make sure migration is the right answer. If your shared hosting TTFB is under 600ms, your site loads in under 3 seconds, and you're not hitting resource limits — you might not need VPS yet.
Migrate when you see:
- Consistent TTFB above 800ms
- "Resource limit exceeded" errors or CPU throttling from your host
- Traffic growing beyond 20,000–30,000 monthly visitors
- Need for custom server software (Redis, custom PHP config, Node.js)
- WooCommerce slow queries your host can't resolve
Step 1: Full backup before touching anything
This is non-negotiable. Take a complete backup from your current shared host before any other step:
bash# Files backup tar -czf site-files-backup.tar.gz /home/username/public_html # Database backup mysqldump -u dbuser -p dbname > db-backup.sql
Store a copy off-server — download it to your local machine or upload to cloud storage. Your old host's backup is not sufficient; if something goes wrong mid-migration, you need a copy you control.
Also note your current settings:
- PHP version (check in cPanel or phpinfo())
- MySQL/MariaDB version
- Any custom PHP.ini settings
- Cron jobs running on the old server
Step 2: Provision and harden your VPS first
Set up your VPS completely before touching your live site. This means:
bashsudo apt update && sudo apt upgrade -y
Security baseline (SSH keys, UFW, fail2ban) — see our Linux VPS security guide.
Then install your web stack. Match PHP and MySQL versions to what your site currently runs:
bashsudo apt install nginx mysql-server php8.2-fpm php8.2-mysql php8.2-curl php8.2-gd php8.2-mbstring php8.2-xml php8.2-zip -y php -v mysql --version
For WordPress, also install:
bashsudo apt install php8.2-imagick php8.2-redis -y
Version matching matters. A WordPress plugin that works on PHP 7.4 may behave differently on PHP 8.2. Better to discover this in testing than after cutover.
Step 3: Configure Nginx and create web root
bashsudo mkdir -p /var/www/example.com/public_html sudo chown -R www-data:www-data /var/www/example.com
Create your Nginx server block (/etc/nginx/sites-available/example.com):
nginxserver { listen 80; server_name example.com www.example.com; root /var/www/example.com/public_html; index index.php index.html; location / { try_files $uri $uri/ /index.php?$args; } location ~ \.php$ { fastcgi_pass unix:/var/run/php/php8.2-fpm.sock; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_name; } }
bashsudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx
Step 4: Copy files and database to VPS
bash# Copy files scp site-files-backup.tar.gz user@your-vps-ip:/tmp/ # Copy database scp db-backup.sql user@your-vps-ip:/tmp/
On the VPS:
bash# Extract files sudo tar -xzf /tmp/site-files-backup.tar.gz -C /var/www/example.com/public_html --strip-components=1 sudo chown -R www-data:www-data /var/www/example.com # Import database sudo mysql -u root -p -e "CREATE DATABASE exampledb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" sudo mysql -u root -p -e "CREATE USER 'dbuser'@'localhost' IDENTIFIED BY 'strong-password';" sudo mysql -u root -p -e "GRANT ALL ON exampledb.* TO 'dbuser'@'localhost';" sudo mysql -u root -p exampledb < /tmp/db-backup.sql
Update your wp-config.php (or app config) with the new database credentials:
phpdefine('DB_HOST', 'localhost'); define('DB_NAME', 'exampledb'); define('DB_USER', 'dbuser'); define('DB_PASSWORD', 'strong-password');
Step 5: Test with /etc/hosts before touching DNS
This is the step most people skip and then regret. Before changing DNS, point your local machine to the new server by editing your hosts file:
On Mac/Linux: /etc/hosts
On Windows: C:\Windows\System32\drivers\etc\hosts
Add:
bashYOUR_VPS_IP example.com www.example.com
Now open example.com in your browser — you're actually hitting the new VPS. Test everything:
- [ ] Homepage loads correctly
- [ ] WordPress admin login works
- [ ] All pages and posts render
- [ ] Contact forms work
- [ ] WooCommerce checkout (if applicable)
- [ ] Images and media load
- [ ] No PHP errors in logs (
sudo tail -f /var/log/nginx/error.log)
Fix any issues here before DNS cutover. This is your last safe testing window.
When done, remove or comment out the hosts file entry.
Step 6: Reduce DNS TTL 24 hours before cutover
Log into your DNS provider and lower your A record TTL to 300 seconds (5 minutes). This needs to be done 12–24 hours in advance, because the current TTL determines how long DNS resolvers cache the old value.
If your current TTL is 86400 (24 hours) and you change the A record now, some users will still hit the old server for up to 24 hours. Lowering TTL first makes the switch near-instant.
Step 7: Install SSL on the VPS before cutover
Install Certbot and get your SSL certificate ready:
bashsudo apt install certbot python3-certbot-nginx -y
You can't run certbot --nginx until DNS points to your VPS (Let's Encrypt validates via HTTP). So have this ready to run immediately after DNS propagates:
bashsudo certbot --nginx -d example.com -d www.example.com
Step 8: The cutover
Choose a low-traffic window — early morning on a weekday works for most business sites. For ecommerce, check your analytics for the slowest hour of the week.
Sequence:
- Put old site in maintenance mode (optional but cleaner)
- Take a final incremental backup — sync any new database changes since your initial backup
- Update DNS A record to your VPS IP
- Run Certbot as soon as the domain resolves to the new IP
- Monitor error logs on the new server for 30 minutes
- Test from a mobile device (different DNS resolver than your computer)
bash# Watch nginx error log in real time: sudo tail -f /var/log/nginx/error.log # Watch PHP errors: sudo tail -f /var/log/php8.2-fpm.log
Step 9: Keep old hosting active for 48–72 hours
Do not cancel your old shared hosting immediately. Keep it active for at least 48–72 hours after DNS change. DNS propagation is not instant globally — some users may still reach the old server for a day or two.
After 72 hours, if everything is running clean on the VPS, you can safely cancel old hosting.
Checklist summary
Before migration:
- [ ] Full file and database backup taken and stored off-server
- [ ] PHP/MySQL versions documented
- [ ] Cron jobs documented
VPS setup:
- [ ] Security baseline applied
- [ ] Nginx + PHP-FPM + MySQL installed with matching versions
- [ ] Web root created, permissions set
Migration:
- [ ] Files and database copied and imported
- [ ] App config updated with new DB credentials
- [ ] Tested with /etc/hosts before DNS change
Cutover:
- [ ] DNS TTL lowered 24h in advance
- [ ] Certbot ready to run
- [ ] Cutover done in low-traffic window
- [ ] Error logs monitored post-cutover
Cleanup:
- [ ] Old hosting kept active 48–72h
- [ ] SSL confirmed working
- [ ] Search Console submitted (if domain changed)
Common migration problems and fixes
Images not loading — Usually a file permissions issue. Run: sudo chown -R www-data:www-data /var/www/example.com
WordPress says "database connection error" — Check wp-config.php database credentials. Confirm MySQL user has correct grants.
PHP errors that didn't exist on shared hosting — Version mismatch. Check error_log and update deprecated function calls or plugin versions.
Redirect loops — Nginx + WordPress permalink mismatch. Make sure try_files $uri $uri/ /index.php?$args; is in your Nginx config.
Moving to HostAccent VPS? Our team can help with migration for any plan. Clean Ubuntu images, NVMe storage, and support that actually knows Linux.











Discussion
Have a question or tip about this topic? Share it below — your comment will appear after review.