The Complete Guide to Migrating Your Website from Shared Hosting to VPS
Moving your website from shared hosting to a VPS is one of the most impactful decisions you'll make as a site owner or developer. It's the moment your online presence grows from a shared apartment to your own building — with full control over the infrastructure, no noisy neighbors competing for resources, and the ability to configure everything exactly how you need it.
But migration is also where things go wrong. Done poorly, it means hours of downtime, broken email, lost SEO rankings, or data loss. Done correctly, it's a seamless transition that your visitors never notice.
This guide walks through every step of a complete website migration: pre-migration planning, VPS setup, web server configuration, database migration, file transfer, DNS cutover, and post-migration verification.
Table of Contents
- Why Migrate from Shared Hosting to VPS?
- Pre-Migration Planning Checklist
- Choosing Your VPS Provider and Plan
- Initial VPS Setup and Security
- Installing the Web Stack (LAMP/LEMP)
- Migrating Your Website Files
- Migrating Your Database
- Configuring Your Web Server (Nginx/Apache)
- Setting Up SSL/TLS Certificates
- Email Migration Considerations
- DNS Cutover: The Critical Step
- Post-Migration Verification
- Performance Optimization After Migration
- Common Migration Problems and Fixes
- Migration Checklist
- FAQ
- Conclusion
- Additional SEO Data
Why Migrate from Shared Hosting to VPS?
Before diving into the how, it's worth understanding the why — because migration is not trivial, and you should be confident it's the right move.
Signs You've Outgrown Shared Hosting
- Slow page loads: Your site shares CPU and RAM with hundreds of other sites. When they get traffic spikes, your performance suffers.
- Resource limits hit: You're seeing errors like "500 Internal Server Error" or "508 Resource Limit Reached" during traffic spikes.
- PHP/MySQL version locked: Your hosting provider forces old PHP 7.x when your application needs 8.2+.
- You can't install software: Need Redis for caching, custom PHP extensions, or Node.js? Not possible on shared hosting.
- Traffic growth: Your site now handles thousands of daily visitors and needs dedicated resources.
- Security concerns: You share an IP with other sites — if they get blacklisted, your email deliverability suffers too.
- Compliance requirements: GDPR, HIPAA, or PCI-DSS requirements that shared hosting cannot meet.
What You Gain with a VPS
- Dedicated resources: Your RAM and CPU allocation is yours — no neighbors can steal it
- Root access: Install any software, configure any service, set any kernel parameter
- Custom PHP/MySQL/Node versions: Run exactly what your application needs
- Better performance: NVMe SSDs, dedicated network, no contention
- Scalability: Upgrade RAM and CPU in minutes through your provider's control panel
- Better security: Your own firewall, your own SSL certificates, your own network isolation
- Cost efficiency at scale: A VPS that handles 100,000 monthly visitors costs the same as shared hosting that struggled at 10,000
Pre-Migration Planning Checklist
Rushing into migration is the most common cause of downtime and data loss. Spend time on planning — it pays dividends during the actual migration.
1. Inventory Your Current Setup
# On your current shared hosting, document:
# PHP version
php -v
# PHP extensions in use (check phpinfo() or php.ini)
php -m
# MySQL/MariaDB version
mysql --version
# List all databases
mysql -u root -p -e "SHOW DATABASES;"
# Disk space used
du -sh ~/public_html/
du -sh ~/
# Number of files
find ~/public_html -type f | wc -l
Document all of this in a migration notes file. You'll need it when configuring the VPS.
2. Map Your Website Components
For each website being migrated, note:
- Domain names and who controls the DNS (registrar login needed)
- Document root path (e.g.,
/public_html/,/public_html/domain.com/) - Database name(s), username(s), password(s)
- Email accounts (if hosted on shared hosting)
- SSL certificates (Let's Encrypt? Paid? Provider-managed?)
- Cron jobs — list all scheduled tasks
- Special server configurations —
.htaccessrules, custom PHP settings - External services — payment gateways, CDNs, APIs that have IP whitelisting
3. Check for Dependencies
# WordPress: check active plugins and their server requirements
# Joomla/Drupal: check module requirements
# Custom apps: check composer.json or requirements.txt
# Look for these in your current shared hosting:
# - ionCube loader (encrypted PHP files)
# - ImageMagick vs GD
# - Specific PHP extensions: mbstring, curl, gd, zip, xml
# - Specific Apache modules (mod_rewrite, etc.)
4. Schedule Your Migration Window
- Choose a low-traffic period (typically 2-6 AM local time, or a Sunday)
- TTL reduction: 48 hours before migration, lower your DNS TTL to 300 seconds (5 minutes)
- This ensures DNS changes propagate quickly during cutover
- Log into your registrar/DNS provider and change TTL on all A records
- Notify stakeholders (if it's a business site)
- Prepare a rollback plan: keep shared hosting active for at least 72 hours after migration
5. Create Full Backups Before Starting
# On shared hosting — backup everything before touching anything
# Method 1: cPanel backup (if available)
# Login to cPanel > Backup Wizard > Full Backup
# Method 2: Manual backup via SSH on shared hosting
tar -czf ~/backup_files_$(date +%Y%m%d).tar.gz ~/public_html/
mysqldump -u dbuser -p dbname > ~/backup_db_$(date +%Y%m%d).sql
# Download backups to your local machine
scp user@sharedhost:~/backup_files_*.tar.gz ./
scp user@sharedhost:~/backup_db_*.sql ./
Choosing Your VPS Provider and Plan
Recommended VPS Providers (2026)
| Provider | Starting Price | Best For | Notable Feature |
|---|---|---|---|
| Hetzner | ~$4/month | Price/performance | Excellent European data centers |
| DigitalOcean | $6/month | Beginners | Clean UI, good documentation |
| Vultr | $6/month | Global reach | 32 locations worldwide |
| Linode (Akamai) | $5/month | Stability | Excellent long-term reliability |
| AWS Lightsail | $3.50/month | AWS ecosystem | Easy upgrade path to EC2 |
| OVHcloud | $3.50/month | Budget | Good EU compliance options |
How Much VPS Do You Need?
For WordPress/CMS sites:
- Up to 10,000 monthly visitors: 2 vCPU, 2GB RAM, 40GB SSD
- 10,000–100,000 monthly visitors: 4 vCPU, 4GB RAM, 80GB SSD
- 100,000+ monthly visitors: 8 vCPU, 8GB RAM, 160GB SSD (or consider managed hosting)
For custom applications:
- Depends heavily on the app. Start with 2 vCPU / 2GB RAM and monitor.
Storage calculation:
# Current site size + database size + 3x growth buffer + OS overhead
# Example: 5GB files + 2GB database = 7GB current → choose 40GB minimum
Choose Your Server Location
Pick a data center close to your primary audience:
- US East (New York, Ashburn) for US East Coast and European audiences
- US West (Los Angeles, San Jose) for US West Coast and Asia-Pacific
- EU (Frankfurt, Amsterdam) for European audiences
- Singapore, Tokyo for Southeast Asian audiences
Use ping.canopy.tools or your provider's speed test to verify latency.
Initial VPS Setup and Security
Before migrating anything, set up and harden your VPS. You're building the foundation.
Step 1: Initial Login and System Update
# Connect to your new VPS (initial root login from provider)
ssh root@your-vps-ip
# Update all system packages
apt update && apt upgrade -y # Ubuntu/Debian
dnf update -y # AlmaLinux/Rocky Linux
# Set server timezone
timedatectl set-timezone America/New_York # Or your timezone
timedatectl status
Step 2: Create a Non-Root User
# Never run your web server as root
adduser webadmin
usermod -aG sudo webadmin
# Copy root's SSH key to the new user
mkdir -p /home/webadmin/.ssh
cp /root/.ssh/authorized_keys /home/webadmin/.ssh/
chown -R webadmin:webadmin /home/webadmin/.ssh
chmod 700 /home/webadmin/.ssh
chmod 600 /home/webadmin/.ssh/authorized_keys
# Test login as new user in a NEW terminal before proceeding
ssh webadmin@your-vps-ip
sudo whoami # Should return: root
Step 3: SSH Hardening
sudo nano /etc/ssh/sshd_config
# Set these values:
# PermitRootLogin no
# PasswordAuthentication no
# Port 2222 (optional: change from default 22)
# Test config
sudo sshd -t
# Reload SSH
sudo systemctl reload sshd
Step 4: Configure UFW Firewall
# Install UFW (Ubuntu/Debian)
sudo apt install ufw -y
# Default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow SSH (on your port)
sudo ufw allow 2222/tcp comment 'SSH'
# Allow web traffic
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'
# Allow MySQL only from localhost (no external access)
# MySQL should NEVER be open to the internet
# sudo ufw allow from 10.0.0.0/8 to any port 3306 # only if needed from another server
# Enable firewall
sudo ufw enable
# Verify rules
sudo ufw status verbose
Step 5: Install Fail2ban
sudo apt install fail2ban -y
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Installing the Web Stack (LAMP/LEMP)
Choose your stack based on your application:
- LAMP = Linux + Apache + MySQL + PHP
- LEMP = Linux + Nginx + MySQL/MariaDB + PHP-FPM
Nginx (LEMP) is recommended for most use cases: better performance, lower memory usage, and superior handling of concurrent connections.
Installing Nginx
# Install Nginx
sudo apt install nginx -y
# Start and enable
sudo systemctl start nginx
sudo systemctl enable nginx
# Verify it's running
sudo systemctl status nginx
curl -I <http://localhost> # Should return HTTP/1.1 200 OK
# Check Nginx version
nginx -v
Installing MySQL 8.0
# Install MySQL
sudo apt install mysql-server -y
# Secure the installation
sudo mysql_secure_installation
# Follow prompts:
# - Set root password: YES
# - Remove anonymous users: YES
# - Disallow root login remotely: YES
# - Remove test database: YES
# - Reload privilege tables: YES
# Start and enable
sudo systemctl start mysql
sudo systemctl enable mysql
# Verify
sudo mysql -u root -p -e "SELECT version();"
# Create a database and user for your site
sudo mysql -u root -p << 'EOF'
CREATE DATABASE mysite_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'mysite_user'@'localhost' IDENTIFIED BY 'StrongPassword123!';
GRANT ALL PRIVILEGES ON mysite_db.* TO 'mysite_user'@'localhost';
FLUSH PRIVILEGES;
EOF
Installing PHP 8.2 (with FPM for Nginx)
# Add PHP repository (for latest versions)
sudo apt install software-properties-common -y
sudo add-apt-repository ppa:ondrej/php # Ubuntu
sudo apt update
# Install PHP 8.2 with common extensions
sudo apt install php8.2-fpm php8.2-mysql php8.2-xml php8.2-mbstring \
php8.2-curl php8.2-zip php8.2-gd php8.2-intl php8.2-redis \
php8.2-bcmath php8.2-soap php8.2-imagick -y
# Start PHP-FPM
sudo systemctl start php8.2-fpm
sudo systemctl enable php8.2-fpm
# Verify
php -v
php -m | grep -E 'mysql|curl|gd|mbstring|zip|xml'
# Configure PHP settings for web
sudo nano /etc/php/8.2/fpm/php.ini
# Key settings to adjust:
# upload_max_filesize = 64M
# post_max_size = 64M
# memory_limit = 256M
# max_execution_time = 300
# date.timezone = America/New_York
# Apply PHP config changes
sudo systemctl reload php8.2-fpm
Installing Apache (Alternative to Nginx)
# If you prefer Apache (LAMP stack)
sudo apt install apache2 -y
# Enable essential modules
sudo a2enmod rewrite ssl headers
# For PHP with Apache (mod_php)
sudo apt install libapache2-mod-php8.2 php8.2 php8.2-mysql -y
# Start and enable
sudo systemctl start apache2
sudo systemctl enable apache2
Migrating Your Website Files
Now the actual migration begins. This is a two-step process: copy files to the VPS, then verify them.
Method 1: rsync (Recommended — Fast and Resumable)
# From your LOCAL machine (not the VPS), run:
# First: download from shared hosting to local
rsync -avz --progress \
-e "ssh -p 22" \
user@sharedhost.example.com:/home/user/public_html/ \
./local_backup/public_html/
# Then: upload from local to VPS
rsync -avz --progress \
-e "ssh -p 2222" \
./local_backup/public_html/ \
webadmin@your-vps-ip:/var/www/yourdomain.com/public_html/
# OR: Direct server-to-server transfer (if your VPS can reach the shared host)
# On the VPS:
rsync -avz --progress \
-e "ssh -p 22" \
user@sharedhost.example.com:/home/user/public_html/ \
/var/www/yourdomain.com/public_html/
Method 2: SCP
# Download from shared hosting
scp -r user@sharedhost.example.com:/home/user/public_html/ ./local_backup/
# Upload to VPS
scp -r ./local_backup/public_html/ webadmin@your-vps-ip:/var/www/yourdomain.com/
Method 3: FTP/FTPS Download + Upload
If your shared host doesn't support SSH:
# Use FileZilla or similar FTP client:
# 1. Connect to shared hosting via FTP
# 2. Download all files to local machine
# 3. Upload to VPS via SFTP
# For command-line FTP download:
wget -r --ftp-user=ftpuser --ftp-password=ftppass <ftp://sharedhost.example.com/public_html/>
Setting Up the Web Root Directory
# Create directory structure on VPS
sudo mkdir -p /var/www/yourdomain.com/public_html
sudo mkdir -p /var/www/yourdomain.com/logs
# Set ownership to web server user
sudo chown -R www-data:www-data /var/www/yourdomain.com
# Set correct permissions
# Directories: 755 (readable by web server)
# Files: 644 (readable but not writable by web server)
sudo find /var/www/yourdomain.com -type d -exec chmod 755 {} \;
sudo find /var/www/yourdomain.com -type f -exec chmod 644 {} \;
# For WordPress: specific writable directories
sudo chmod -R 775 /var/www/yourdomain.com/public_html/wp-content/uploads/
sudo chmod -R 775 /var/www/yourdomain.com/public_html/wp-content/cache/
# Verify file count matches source
find /var/www/yourdomain.com/public_html -type f | wc -l
# Compare with count from shared hosting
Verify File Integrity
# Generate checksums on source (shared hosting)
find ~/public_html -type f -exec md5sum {} \; > ~/file_checksums.txt
# Download checksums file
scp user@sharedhost:~/file_checksums.txt ./
# Generate checksums on destination (VPS)
find /var/www/yourdomain.com/public_html -type f -exec md5sum {} \; > /tmp/vps_checksums.txt
# Compare (adjust paths as needed)
diff <(awk '{print $1}' ./file_checksums.txt | sort) \
<(awk '{print $1}' /tmp/vps_checksums.txt | sort)
# No output = files match
Migrating Your Database
Database migration is the most critical step — this is your data. Take it seriously.
Step 1: Export from Shared Hosting
# Method 1: Via SSH on shared hosting (preferred)
mysqldump \
--user=dbuser \
--password=dbpassword \
--host=localhost \
--single-transaction \
--routines \
--triggers \
--events \
--hex-blob \
--default-character-set=utf8mb4 \
dbname > backup_dbname_$(date +%Y%m%d_%H%M%S).sql
# Verify the dump is complete (should end with these lines)
tail -5 backup_dbname_*.sql
# Should show: -- Dump completed on YYYY-MM-DD HH:MM:SS
# Check dump file size
ls -lh backup_dbname_*.sql
# Method 2: Via phpMyAdmin (if no SSH access)
# Database > Export > Custom
# Format: SQL
# Add DROP TABLE/IF EXISTS: checked
# Export to file
Step 2: Transfer the Dump File
# Download to local machine
scp user@sharedhost:~/backup_dbname_*.sql ./
# Upload to VPS
scp ./backup_dbname_*.sql webadmin@your-vps-ip:~/
# Or compress for faster transfer (large databases)
gzip backup_dbname_*.sql
scp ./backup_dbname_*.sql.gz webadmin@your-vps-ip:~/
Step 3: Import to VPS MySQL
# On the VPS:
# First, create the target database and user (if not done earlier)
sudo mysql -u root -p << 'EOF'
CREATE DATABASE mysite_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'mysite_user'@'localhost' IDENTIFIED BY 'StrongNewPassword!';
GRANT ALL PRIVILEGES ON mysite_db.* TO 'mysite_user'@'localhost';
FLUSH PRIVILEGES;
EOF
# Import the SQL dump
mysql -u mysite_user -p mysite_db < ~/backup_dbname_*.sql
# If compressed:
gunzip -c ~/backup_dbname_*.sql.gz | mysql -u mysite_user -p mysite_db
# Verify import was successful
mysql -u mysite_user -p mysite_db -e "SHOW TABLES;"
mysql -u mysite_user -p mysite_db -e "SELECT COUNT(*) FROM your_main_table;"
# Compare row counts with source database
Step 4: Update Database Connection in Your Application
# WordPress: update wp-config.php
nano /var/www/yourdomain.com/public_html/wp-config.php
# Update these values:
# define('DB_NAME', 'mysite_db');
# define('DB_USER', 'mysite_user');
# define('DB_PASSWORD', 'StrongNewPassword!');
# define('DB_HOST', 'localhost');
# Laravel: update .env
nano /var/www/yourdomain.com/public_html/.env
# DB_CONNECTION=mysql
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=mysite_db
# DB_USERNAME=mysite_user
# DB_PASSWORD=StrongNewPassword!
# General PHP application: update database config file
# (location varies by framework/CMS)
Large Database Migration (100GB+)
For very large databases, the standard dump/import may be too slow:
# Option 1: Use mydumper/myloader (parallel, much faster)
sudo apt install mydumper -y
# Export with mydumper (on source)
mydumper \
--user=dbuser \
--password=dbpass \
--database=dbname \
--outputdir=/tmp/db_backup \
--threads=4 \
--compress
# Import with myloader (on VPS)
myloader \
--user=mysite_user \
--password=dbpass \
--database=mysite_db \
--directory=/tmp/db_backup \
--threads=4
# Option 2: Percona XtraBackup (for hot backups, no downtime)
# sudo apt install percona-xtrabackup-80
# innobackupex --user=root --password=pass /tmp/xtrabackup/
Configuring Your Web Server (Nginx/Apache)
Nginx Virtual Host Configuration
# Create site configuration
sudo nano /etc/nginx/sites-available/yourdomain.com
# /etc/nginx/sites-available/yourdomain.com
server {
listen 80;
listen [::]:80;
server_name yourdomain.com www.yourdomain.com;
# Redirect HTTP to HTTPS (uncomment after SSL is set up)
# return 301 https://$server_name$request_uri;
root /var/www/yourdomain.com/public_html;
index index.php index.html index.htm;
# Logging
access_log /var/www/yourdomain.com/logs/access.log;
error_log /var/www/yourdomain.com/logs/error.log;
# WordPress permalinks / general URL rewriting
location / {
try_files $uri $uri/ /index.php?$args;
}
# PHP processing via PHP-FPM
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
# Timeouts for long-running PHP scripts
fastcgi_read_timeout 300;
}
# Deny access to hidden files
location ~ /\. {
deny all;
}
# Deny access to sensitive files
location ~* /(wp-config.php|\.env|\.git|composer\.json) {
deny all;
}
# Cache static files
location ~* \.(jpg|jpeg|png|gif|ico|css|js|pdf|woff|woff2|ttf|svg)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
# File upload size limit
client_max_body_size 64M;
}
# Enable the site
sudo ln -s /etc/nginx/sites-available/yourdomain.com /etc/nginx/sites-enabled/
# Remove default site
sudo rm /etc/nginx/sites-enabled/default
# Test Nginx configuration
sudo nginx -t
# Reload Nginx
sudo systemctl reload nginx
Apache Virtual Host Configuration
sudo nano /etc/apache2/sites-available/yourdomain.com.conf
<VirtualHost *:80>
ServerName yourdomain.com
ServerAlias www.yourdomain.com
DocumentRoot /var/www/yourdomain.com/public_html
ErrorLog /var/www/yourdomain.com/logs/error.log
CustomLog /var/www/yourdomain.com/logs/access.log combined
<Directory /var/www/yourdomain.com/public_html>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
# PHP settings
php_value upload_max_filesize 64M
php_value post_max_size 64M
php_value memory_limit 256M
</VirtualHost>
# Enable the site
sudo a2ensite yourdomain.com.conf
sudo a2dissite 000-default.conf
# Test Apache configuration
sudo apache2ctl configtest
# Reload Apache
sudo systemctl reload apache2
Setting Up SSL/TLS Certificates
Free SSL with Let's Encrypt via Certbot:
# Install Certbot
sudo apt install certbot python3-certbot-nginx -y # Nginx
sudo apt install certbot python3-certbot-apache -y # Apache
# IMPORTANT: Before running Certbot, your domain's DNS must already
# point to this VPS IP. This is why you might want to do SSL setup
# AFTER DNS cutover, or use DNS validation.
# Option A: HTTP validation (requires DNS already pointing to VPS)
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
# or for Apache:
sudo certbot --apache -d yourdomain.com -d www.yourdomain.com
# Option B: DNS validation (works BEFORE DNS cutover — recommended for migration)
sudo certbot certonly \
--manual \
--preferred-challenges dns \
-d yourdomain.com \
-d www.yourdomain.com
# Certbot will give you a TXT record to add to DNS
# After adding it and verifying, cert is issued
# Verify certificate was issued
sudo ls /etc/letsencrypt/live/yourdomain.com/
# Test renewal (don't actually renew, just test)
sudo certbot renew --dry-run
# View certificate expiry
sudo certbot certificates
# Auto-renewal is set up via systemd timer:
sudo systemctl status certbot.timer
# Certs auto-renew when less than 30 days from expiry
Update Nginx Config for HTTPS
# /etc/nginx/sites-available/yourdomain.com (after SSL setup)
server {
listen 80;
listen [::]:80;
server_name yourdomain.com www.yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
# SSL certificates (from Certbot)
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
root /var/www/yourdomain.com/public_html;
index index.php index.html;
# ... rest of configuration ...
}
Email Migration Considerations
Email is the most commonly overlooked part of migration — and the most painful if done wrong.
Option A: Keep Email on Shared Hosting (Easiest)
If your shared hosting plan includes email, you don't have to move it.
# In your DNS, keep MX records pointing to shared hosting:
# MX 10 mail.yourdomain.com (keep pointing to shared host)
# Only change A record to point to new VPS
# DNS split: web traffic → VPS, email → shared hosting
Option B: Move to Third-Party Email (Recommended)
Don't run your own mail server if you can avoid it — email deliverability is complex.
Recommended options:
- Google Workspace ($6/user/month) — best deliverability, familiar interface
- Zoho Mail (free for up to 5 users) — good free option
- Microsoft 365 ($6/user/month) — good for Windows-heavy teams
- Fastmail ($5/user/month) — privacy-focused
# After setting up Google Workspace or Zoho:
# Update DNS records as provided by the email service:
# MX records → point to their mail servers
# SPF TXT record → their SPF entry
# DKIM TXT record → their DKIM key
# DMARC TXT record → their DMARC policy
Option C: Run Your Own Mail Server (Expert Only)
Running a mail server on a VPS is complex — you need to configure Postfix/Dovecot, SPF/DKIM/DMARC, maintain IP reputation, and deal with blacklisting.
# If you must run your own (basic Postfix + Dovecot setup):
sudo apt install postfix dovecot-core dovecot-imapd dovecot-pop3d -y
# This is a topic for its own guide — not recommended unless you
# have significant sysadmin experience
DNS Cutover: The Critical Step
DNS cutover is the moment your domain starts pointing to the new VPS. This is the most nerve-wracking part of migration — but with preparation, it's smooth.
Pre-Cutover Checklist
Before changing any DNS, verify on the VPS:
# Test site works by modifying your LOCAL /etc/hosts file
# (This makes your machine resolve the domain to VPS without changing DNS globally)
# On your LOCAL machine:
sudo nano /etc/hosts # Linux/macOS
# C:\Windows\System32\drivers\etc\hosts # Windows (run as admin)
# Add this line:
# your-vps-ip yourdomain.com www.yourdomain.com
# Now visit <https://yourdomain.com> in your browser
# You'll see the VPS version of the site
# Verify:
# ✓ Homepage loads correctly
# ✓ Login works
# ✓ Dynamic pages work (blog posts, product pages)
# ✓ Forms work
# ✓ File uploads work
# ✓ SSL certificate is valid
# ✓ Redirects work (www → non-www or vice versa)
# ✓ No broken images or 404 errors
# Remove the /etc/hosts entry after testing
Performing the DNS Cutover
# Step 1: Verify current TTL (should be 300 from your prep 48h ago)
dig yourdomain.com +ttl | grep -A 1 'ANSWER SECTION'
# Should show TTL: 300 (5 minutes)
# Step 2: Log into your DNS provider (Cloudflare, Route53, registrar, etc.)
# Update A record:
# yourdomain.com A your-vps-ip
# www.yourdomain.com A your-vps-ip
# Step 3: Monitor propagation
# Tool 1: Check from multiple locations
curl -s <https://api.hackertarget.com/dnslookup/?q=yourdomain.com>
# Tool 2: dig against multiple DNS servers
dig @8.8.8.8 yourdomain.com A # Google DNS
dig @1.1.1.1 yourdomain.com A # Cloudflare DNS
dig @9.9.9.9 yourdomain.com A # Quad9 DNS
# Tool 3: Online propagation checker
# <https://www.whatsmydns.net/#A/yourdomain.com>
# <https://dnschecker.org/#A/yourdomain.com>
# Step 4: Monitor continuously for 30 minutes
watch -n 30 'dig @8.8.8.8 yourdomain.com A +short'
If Using Cloudflare (Highly Recommended)
# Cloudflare DNS is the best DNS option for migration:
# - Sub-second propagation (they push changes globally instantly)
# - Free CDN and DDoS protection
# - Easy rollback (just change the IP back)
# - 5-minute TTL by default
# To migrate to Cloudflare:
# 1. Add site to Cloudflare (free tier)
# 2. Import existing DNS records
# 3. Change nameservers at registrar to Cloudflare nameservers
# 4. When propagated, change A record to new VPS IP in Cloudflare
# Cloudflare proxy (orange cloud) vs DNS-only (gray cloud):
# Orange cloud = traffic goes through Cloudflare (hides your VPS IP, CDN benefits)
# Gray cloud = direct DNS resolution to VPS IP
# Start with gray cloud during migration, enable orange cloud after verification
Post-Migration Verification
After DNS cutover, perform systematic verification before declaring success.
Functional Verification
# 1. Verify the correct server is being hit
curl -I <https://yourdomain.com>
# Look for server header (nginx or apache)
# Verify SSL certificate is valid and from correct provider
# 2. Check SSL certificate details
curl -vI <https://yourdomain.com> 2>&1 | grep -A 10 'SSL connection'
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com 2>/dev/null | openssl x509 -noout -text | grep -A 3 'Validity'
# 3. Check HTTP to HTTPS redirect
curl -I <http://yourdomain.com>
# Should show: HTTP/1.1 301 Moved Permanently
# Location: <https://yourdomain.com/>
# 4. Check www redirect
curl -I <http://www.yourdomain.com>
curl -I <https://www.yourdomain.com>
# Should redirect to your canonical URL
# 5. Check response time
curl -w "\nTime: %{time_total}s\n" -o /dev/null -s <https://yourdomain.com>
# Compare with old shared hosting response time
# 6. Verify database connection (for CMS/app)
curl -s <https://yourdomain.com> | grep -i 'error\|database\|connection'
# Should return no database errors
WordPress-Specific Verification
# Check WordPress is serving from correct URL
curl -s <https://yourdomain.com> | grep 'yourdomain.com' | head -5
# If WordPress shows wrong URLs (old shared hosting IP):
# Update WordPress URLs in database
sudo mysql -u mysite_user -p mysite_db << 'EOF'
UPDATE wp_options SET option_value = '<https://yourdomain.com>' WHERE option_name = 'siteurl';
UPDATE wp_options SET option_value = '<https://yourdomain.com>' WHERE option_name = 'home';
EOF
# If WordPress serialized URLs point to old domain, use WP-CLI:
sudo wp --path=/var/www/yourdomain.com/public_html search-replace '<http://yourdomain.com>' '<https://yourdomain.com>' --all-tables
# Flush WordPress cache
sudo wp --path=/var/www/yourdomain.com/public_html cache flush
# Flush Nginx cache (if using FastCGI cache)
sudo rm -rf /var/cache/nginx/*
sudo nginx -s reload
Performance Verification
# Install monitoring tools
sudo apt install htop iotop nethogs -y
# Monitor resource usage
htop
# Check Nginx error log for issues
sudo tail -50 /var/www/yourdomain.com/logs/error.log
sudo tail -50 /var/log/nginx/error.log
# Check PHP-FPM logs
sudo tail -50 /var/log/php8.2-fpm.log
# Check MySQL slow query log (enable in /etc/mysql/mysql.conf.d/mysqld.cnf)
# slow_query_log = 1
# slow_query_log_file = /var/log/mysql/slow.log
# long_query_time = 2
# Test with a load simulation tool
sudo apt install siege -y
siege -c 10 -t 1M <https://yourdomain.com>
# -c 10 = 10 concurrent users
# -t 1M = run for 1 minute
Set Up Cron Jobs
# Recreate any cron jobs from shared hosting
crontab -e
# Common examples:
# WordPress cron
# */5 * * * * php /var/www/yourdomain.com/public_html/wp-cron.php
# Daily database backup
# 0 2 * * * mysqldump -u mysite_user -pPassword mysite_db | gzip > /home/webadmin/backups/db_$(date +\%Y\%m\%d).sql.gz
# Clean old backups (keep 30 days)
# 0 3 * * * find /home/webadmin/backups -mtime +30 -delete
Performance Optimization After Migration
Now that you're on a VPS, you can implement optimizations that were impossible on shared hosting.
PHP OpCache
# Enable OpCache (dramatically speeds up PHP)
sudo nano /etc/php/8.2/fpm/conf.d/10-opcache.ini
# Add/update:
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.revalidate_freq=2
opcache.fast_shutdown=1
# Restart PHP-FPM
sudo systemctl restart php8.2-fpm
# Verify OpCache is active
php -r "var_dump(function_exists('opcache_get_status'));"
Nginx FastCGI Cache
# Add to /etc/nginx/nginx.conf (http block):
# fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=WORDPRESS:10m inactive=60m max_size=1g;
# Add to site config (server block):
# fastcgi_cache WORDPRESS;
# fastcgi_cache_valid 200 60m;
# fastcgi_cache_use_stale error timeout updating invalid_header http_500;
# fastcgi_cache_key "$scheme$request_method$host$request_uri";
# Create cache directory
sudo mkdir -p /var/cache/nginx
sudo chown www-data:www-data /var/cache/nginx
Redis Object Cache (WordPress)
# Install Redis
sudo apt install redis-server php8.2-redis -y
sudo systemctl enable redis
sudo systemctl start redis
# Install Redis Object Cache plugin in WordPress
# wp plugin install redis-cache --activate
# wp redis enable
# Configure wp-config.php:
# define('WP_REDIS_HOST', '127.0.0.1');
# define('WP_REDIS_PORT', 6379);
# define('WP_CACHE', true);
# Verify Redis is working
redis-cli ping # Should return: PONG
redis-cli monitor # Watch real-time commands
MySQL Performance Tuning
# Edit MySQL configuration
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
# For a 2GB RAM VPS, add/update:
[mysqld]
innodb_buffer_pool_size = 512M # 25% of RAM
innodb_log_file_size = 128M
innodb_flush_method = O_DIRECT
query_cache_type = 0 # Disabled in MySQL 8.0 (removed in 8.0.20)
max_connections = 100
# For a 4GB RAM VPS:
# innodb_buffer_pool_size = 1G
# Restart MySQL
sudo systemctl restart mysql
# Verify settings applied
mysql -u root -p -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"
Common Migration Problems and Fixes
Problem 1: 500 Internal Server Error
# Check web server error logs
sudo tail -50 /var/www/yourdomain.com/logs/error.log
sudo tail -50 /var/log/nginx/error.log
# Common causes:
# 1. File permission issues
ls -la /var/www/yourdomain.com/public_html/
# Fix: sudo chown -R www-data:www-data /var/www/yourdomain.com/
# 2. PHP extension missing
php -m | grep -v '\['
# Fix: sudo apt install php8.2-EXTENSION -y
# 3. Wrong PHP-FPM socket path in Nginx config
ls /var/run/php/
# Should show: php8.2-fpm.sock
# Update Nginx config if version differs
# 4. .htaccess with Apache-specific directives on Nginx
# Nginx ignores .htaccess - rules must be in Nginx config
Problem 2: Database Connection Errors
# Test database connection directly
mysql -u mysite_user -p mysite_db -e "SELECT 1;"
# Common issues:
# 1. Wrong password in config file
# 2. Database name mismatch
# 3. User doesn't have permission
mysql -u root -p -e "SHOW GRANTS FOR 'mysite_user'@'localhost';"
# If permissions are wrong:
mysql -u root -p << 'EOF'
GRANT ALL PRIVILEGES ON mysite_db.* TO 'mysite_user'@'localhost';
FLUSH PRIVILEGES;
EOF
Problem 3: Images or CSS Not Loading
# Check for 404 errors in access log
sudo grep '404' /var/www/yourdomain.com/logs/access.log | tail -20
# Check file paths
ls /var/www/yourdomain.com/public_html/wp-content/uploads/
# Check Nginx is serving from correct document root
grep 'root' /etc/nginx/sites-enabled/yourdomain.com
# Permissions fix
sudo find /var/www/yourdomain.com/public_html -type d -exec chmod 755 {} \;
sudo find /var/www/yourdomain.com/public_html -type f -exec chmod 644 {} \;
Problem 4: WordPress Mixed Content (HTTP/HTTPS)
# If WordPress is still serving some HTTP URLs after HTTPS migration:
# Method 1: Add to wp-config.php
echo "define('FORCE_SSL_ADMIN', true);" >> wp-config.php
# Method 2: Fix in database
mysql -u mysite_user -p mysite_db << 'EOF'
UPDATE wp_options SET option_value = replace(option_value, '<http://yourdomain.com>', '<https://yourdomain.com>') WHERE option_name = 'home' OR option_name = 'siteurl';
UPDATE wp_posts SET post_content = replace(post_content, '<http://yourdomain.com>', '<https://yourdomain.com>');
UPDATE wp_postmeta SET meta_value = replace(meta_value, '<http://yourdomain.com>', '<https://yourdomain.com>');
EOF
# Method 3: Nginx reverse proxy header
# Add to Nginx config location block:
# fastcgi_param HTTPS on;
# fastcgi_param HTTP_X_FORWARDED_PROTO https;
Problem 5: Redirect Loops
# Check Nginx config for conflicting redirects
grep -n 'return 301\|rewrite' /etc/nginx/sites-enabled/yourdomain.com
# Check WordPress redirect settings
mysql -u mysite_user -p mysite_db -e \
"SELECT option_name, option_value FROM wp_options WHERE option_name IN ('siteurl','home');"
# If redirect loop involves CloudFlare:
# In Cloudflare SSL settings: set to 'Full' not 'Flexible'
# Flexible = CF to server is HTTP = causes loop if you also force HTTPS in Nginx
Problem 6: Emails Not Sending from VPS
# Many VPS providers block port 25 (SMTP) by default
# Solution: use an external SMTP relay service
# Install msmtp (lightweight SMTP client)
sudo apt install msmtp msmtp-mta -y
# Configure to use SendGrid, Mailgun, or AWS SES
nano ~/.msmtprc
# account default
# host smtp.sendgrid.net
# port 587
# auth on
# user apikey
# password YOUR_SENDGRID_API_KEY
# tls on
# For WordPress: use WP Mail SMTP plugin
# Configure with your SMTP service credentials
Migration Checklist
Use this checklist for every migration:
Pre-Migration:
- [ ] Full inventory of website components documented
- [ ] All database credentials recorded
- [ ] Full backup created and downloaded locally
- [ ] DNS TTL lowered to 300s (48+ hours before cutover)
- [ ] Migration window scheduled and stakeholders notified
- [ ] VPS provider and plan selected
- [ ] Rollback plan documented
VPS Setup:
- [ ] VPS provisioned and updated
- [ ] Non-root admin user created
- [ ] SSH key authentication configured
- [ ] Root login and password auth disabled
- [ ] UFW firewall configured (ports 80, 443, SSH only)
- [ ] Fail2ban installed
Stack Installation:
- [ ] Web server (Nginx/Apache) installed and running
- [ ] MySQL/MariaDB installed and secured
- [ ] PHP (correct version) installed with all required extensions
- [ ] PHP-FPM configured with correct memory/timeout settings
File Migration:
- [ ] All website files transferred
- [ ] File count verified against source
- [ ] File permissions set correctly (755 dirs, 644 files)
- [ ] Document root ownership set to www-data
Database Migration:
- [ ] Database exported with all tables, routines, and triggers
- [ ] Database imported to VPS
- [ ] Row counts verified
- [ ] Application config updated with new DB credentials
Web Server Config:
- [ ] Virtual host configured for domain
- [ ] PHP-FPM socket path correct
- [ ] URL rewriting configured
- [ ] File upload limits set
- [ ] Security headers added
SSL:
- [ ] SSL certificate obtained (Let's Encrypt or paid)
- [ ] HTTP to HTTPS redirect configured
- [ ] www to non-www (or vice versa) redirect configured
- [ ] SSL Labs test: A or A+ rating
Pre-Cutover Testing:
- [ ] Site tested via /etc/hosts modification
- [ ] All pages load correctly
- [ ] Forms and user authentication work
- [ ] Database read/write operations work
- [ ] SSL certificate is valid
- [ ] No errors in Nginx/PHP logs
DNS Cutover:
- [ ] A records updated to VPS IP
- [ ] www A records updated
- [ ] Propagation verified from multiple DNS servers
- [ ] Old server kept running for 72+ hours
Post-Migration:
- [ ] Site loads correctly from multiple devices and networks
- [ ] SSL certificate valid and auto-renewal configured
- [ ] Cron jobs recreated
- [ ] Email sending tested
- [ ] Monitoring set up (uptime, error alerts)
- [ ] Automated backups configured
- [ ] TTL restored to normal (3600-86400 seconds)
FAQ
1. How long does migration typically take?
For a simple WordPress site (under 2GB files, small database): 2-4 hours including testing. For large sites (20GB+ files, 10GB+ database): plan for 8-24 hours. Most of that time is file transfer — the actual configuration is fast. The biggest time variable is file and database size, your connection speed, and how much testing you do.
2. Will there be downtime during migration?
With proper preparation — almost none. The approach in this guide keeps your old server running until DNS propagates. There's a brief window (during DNS propagation) where some users see the old server and some see the new one. With a 5-minute TTL and Cloudflare, this window is typically under 5 minutes.
3. What happens to SEO during migration?
If you:
- Keep all URLs the same (no URL structure changes)
- Set up proper 301 redirects for any changed URLs
- Keep HTTPS working with a valid certificate
- Keep the site accessible with minimal downtime
...then SEO impact should be minimal to none. Google re-crawls pages and updates its index based on the content, not the server. The bigger risk to SEO is unnecessary downtime or broken pages.
4. What if I have multiple websites on the same shared hosting?
Migrate them one at a time. Configure each as a separate virtual host in Nginx:
# Create a new config file for each domain
sudo nano /etc/nginx/sites-available/domain2.com
# Symlink to sites-enabled
sudo ln -s /etc/nginx/sites-available/domain2.com /etc/nginx/sites-enabled/
Migrate and verify each site independently before changing its DNS.
5. Do I need to cancel my shared hosting immediately after migration?
No — and you shouldn't. Keep shared hosting active for at least 1-2 weeks after migration. This ensures:
- You have a fallback if something goes wrong on the VPS
- Any email still stored on shared hosting can be accessed and exported
- Old DNS caches still pointing to shared hosting are served the old site (better than a broken site)
6. What about WordPress-specific migration tools?
Several plugins can help:
- Duplicator Pro — packages the entire site (files + DB) into an installer archive. Good for smaller sites.
- All-in-One WP Migration — simple drag-and-drop migration, works well for sites under 512MB
- WP Migrate Pro — best for database-specific migrations and URL search/replace
- ManageWP / MainWP — good for managing multiple WordPress sites after migration
For large sites, the manual method in this guide is more reliable than plugins because it doesn't have memory/timeout limits.
7. How do I handle a site using a staging/testing subdomain?
# Add the subdomain to your Nginx config:
server_name yourdomain.com www.yourdomain.com staging.yourdomain.com;
# Or create a separate virtual host:
# /etc/nginx/sites-available/staging.yourdomain.com
# with separate root, logs, and possibly separate DB
# Add DNS A record: staging.yourdomain.com → VPS IP
8. What monitoring should I set up after migration?
# Install netdata for real-time server monitoring
curl <https://my-netdata.io/kickstart.sh> > /tmp/netdata-kickstart.sh
sudo sh /tmp/netdata-kickstart.sh
# Access: <http://your-vps-ip:19999>
# Uptime monitoring (free tier available):
# - UptimeRobot (<https://uptimerobot.com>) — 5-minute checks, free
# - StatusCake — free tier, keyword monitoring
# - Better Uptime — better UI, free tier available
# Set up email alerts from these services to get notified of downtime
Conclusion
Migrating from shared hosting to a VPS is one of those tasks that looks daunting in theory but is entirely manageable with the right process. The key principles that make migration successful:
- Plan before you act — inventory, backup, document everything before touching the first config file
- Lower TTL early — 48 hours before migration, not 10 minutes before. This is the most commonly skipped step.
- Test before you cut over — the /etc/hosts trick lets you verify the VPS thoroughly before any DNS change
- Never shut down the old server prematurely — keep it running for at least a week after migration
- Migrate one site at a time — even if you have 10 sites to move, do them sequentially, verify each one
- Verify systematically — don't just check the homepage. Test logins, forms, email, file uploads, and all major site functions
The migration itself is the easy part. What pays dividends long-term is what you do after: setting up automated backups, monitoring, performance optimization, and a regular security update schedule. A VPS gives you the tools — this guide gives you the starting point.
Call to Action
Have you recently migrated a site to a VPS? What was the most challenging part — the database, the DNS cutover, or something else entirely? Share your experience in the comments.
For more VPS management guides:
- 📌 How to Use SSH Securely to Manage Your VPS Remotely — the foundation for everything in this guide
- 📌 How to Set Up UFW Firewall on Ubuntu: Complete VPS Security Guide — next step after migration
- 📌 25 Linux Commands Every Professional Server Administrator Must Master — command-line skills for ongoing VPS management
Additional SEO Data
Primary Keyword: migrating website from shared hosting to VPS, shared hosting to VPS migration guide
LSI Keywords: website migration tutorial, VPS setup guide, WordPress migration VPS, move website to VPS, DNS cutover website, LEMP stack setup, MySQL migration VPS
Semantic Keywords: web server configuration, VPS hosting benefits, Nginx virtual host, SSL certificate VPS, database migration MySQL, web hosting upgrade
Long Tail Keywords: how to migrate WordPress from shared hosting to VPS, step by step shared hosting to VPS migration, zero downtime website migration tutorial, how to set up Nginx for WordPress on VPS, MySQL dump and restore migration
Question Keywords: How do I move my website from shared hosting to a VPS? Will migrating to a VPS affect my SEO? How long does website migration take? Do I need to cancel shared hosting after migration?
Entity Keywords: cPanel, Nginx, Apache, MySQL, PHP-FPM, Let's Encrypt, Certbot, Cloudflare, rsync, SCP, mysqldump, WordPress, WP-CLI, Fail2ban, UFW, DigitalOcean, Hetzner, Vultr
Meta Title: Complete Guide: Migrating Your Website from Shared Hosting to VPS (2026)
Meta Description: Step-by-step guide to migrate your website from shared hosting to VPS — zero downtime DNS cutover, file transfer, database migration, Nginx setup, SSL, and post-migration optimization.
Slug: complete-guide-migrating-website-shared-hosting-to-vps
Blogger Tags: VPS Migration, Web Hosting, Linux, Nginx, MySQL, WordPress, DNS, SSL, Server Administration, DevOps
Category: VPS Management, Web Hosting, Linux Administration, WordPress
Alt Image: Diagram showing website migration flow from shared hosting to VPS with DNS cutover
Caption Image: Complete shared hosting to VPS migration path — from file transfer and database migration to DNS cutover and post-migration optimization
{
"@context": "<https://schema.org>",
"@type": "Article",
"headline": "The Complete Guide to Migrating Your Website from Shared Hosting to VPS",
"description": "Step-by-step website migration guide from shared hosting to VPS — files, database, Nginx/Apache config, SSL, DNS cutover, and performance optimization.",
"author": {
"@type": "Person",
"name": "CloudAdminHub"
},
"publisher": {
"@type": "Organization",
"name": "CloudAdminHub"
},
"datePublished": "2026-06-15",
"dateModified": "2026-06-15",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "<https://cloudadminhub.blogspot.com/2026/06/complete-guide-migrating-website-shared-hosting-to-vps.html>"
}
}
{
"@context": "<https://schema.org>",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "How long does migrating a website from shared hosting to VPS take?",
"acceptedAnswer": {
"@type": "Answer",
"text": "For a simple WordPress site under 2GB: 2-4 hours including testing. For larger sites (20GB+ files, 10GB+ database): 8-24 hours. Most time is spent on file transfer and verification. The VPS configuration itself typically takes 1-2 hours regardless of site size."
}
},
{
"@type": "Question",
"name": "Will migrating to a VPS affect my SEO?",
"acceptedAnswer": {
"@type": "Answer",
"text": "If done correctly — keeping all URLs the same, setting up proper redirects for any changed URLs, maintaining HTTPS, and minimizing downtime — SEO impact should be minimal to none. VPS migrations often improve SEO over time by providing faster page load speeds."
}
},
{
"@type": "Question",
"name": "Can I migrate my website with zero downtime?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Nearly zero downtime is achievable with proper preparation. Lower your DNS TTL to 300 seconds 48 hours before migration. Keep your old shared hosting running during and after the migration. With a 5-minute TTL and Cloudflare, the transition window is typically under 5 minutes — often invisible to users."
}
}
]
}
OG Title: Complete Website Migration Guide: Shared Hosting to VPS (2026)
OG Description: Migrate your website from shared hosting to VPS with zero downtime — covers file transfer, database migration, Nginx setup, SSL, DNS cutover, and performance optimization.
twitter:card = summary_large_image
twitter:title = How to Migrate Your Website from Shared Hosting to VPS
twitter:description = Step-by-step migration guide: files, database, web server config, SSL, DNS cutover, and post-migration optimization for zero downtime.
Recommended External Links:



Komentar
Posting Komentar