This comprehensive guide walks you through migrating a WordPress site from shared hosting to a VPS and optimizing it for maximum performance.
1. Prerequisites
What You’ll Need:
- VPS Server (e.g., Hetzner, DigitalOcean, Linode)
- Minimum: 2GB RAM, 2 CPU cores
- Recommended: 4GB RAM, 3+ CPU cores
- SSH access to your VPS
- Access to current hosting (cPanel/hPanel)
- Domain registrar access (for DNS changes)
- FTP/SFTP client (FileZilla recommended)
- Basic command line knowledge
VPS Requirements:
- Ubuntu 20.04+ or Debian 11+
- Apache or Nginx installed
- MySQL/MariaDB installed
- PHP 8.0+ installed
- Root or sudo access
2. Prepare Your VPS
Install LAMP Stack
# Connect to VPS
ssh root@your_vps_ip
# Update system
apt update && apt upgrade -y
# Install Apache
apt install apache2 -y
# Install MySQL
apt install mysql-server -y
# Secure MySQL installation
mysql_secure_installation
# Install PHP and extensions
apt install php php-mysql php-curl php-gd php-mbstring php-xml php-xmlrpc php-soap php-intl php-zip -y
# Enable Apache modules
a2enmod rewrite
systemctl restart apache2
# Verify installations
apache2 -v
mysql --version
php -v
Create WordPress Directory
# Set proper permissions
cd /var/www/html
chown -R www-data:www-data /var/www/html
chmod -R 755 /var/www/html
3. Backup Your Current Site
Method 1: Using Hosting Control Panel
Step 1: Download Files
- Log into your hosting control panel (cPanel/hPanel)
- Go to File Manager
- Navigate to your WordPress directory (usually
public_html) - Right-click → Compress → Choose ZIP format
- Wait for compression to complete
- Download the ZIP file to your computer
Step 2: Export Database
- In control panel, go to phpMyAdmin
- Select your WordPress database from left sidebar
- Click Export tab at the top
- Choose Quick export method
- Format: SQL
- Click Go to download
Method 2: Manual FTP + Database Export
Download Files via FTP:
# Use FileZilla or similar FTP client
# Connect to your shared hosting
# Download entire WordPress directory
Export Database:
-- In phpMyAdmin, select database and export
-- OR use command line if available:
mysqldump -u username -p database_name > backup.sql
Method 3: Using Migration Plugins (For Sites Under 500MB)
Using Duplicator:
- Install Duplicator plugin on current site
- Go to Duplicator → Packages → Create New
- Follow wizard to build package
- Download both
installer.phpand.ziparchive
Note: Free version has 500MB limit. For larger sites, use manual method.
4. Migrate to VPS
Upload Files to VPS
Using SCP (Secure Copy):
# From your local machine
scp wordpress-backup.zip root@your_vps_ip:/var/www/html/
scp database-backup.sql root@your_vps_ip:/root/
Or use SFTP client like FileZilla:
- Protocol: SFTP
- Host: your_vps_ip
- Username: root
- Password: your_password
- Port: 22
Extract Files on VPS
# SSH into VPS
ssh root@your_vps_ip
# Clear default WordPress installation if exists
cd /var/www/html
rm -rf *
# Extract your backup
unzip /root/wordpress-backup.zip -d /var/www/html/
# If files are in subdirectory (like public_html), move them
cd /var/www/html
if [ -d "public_html" ]; then
mv public_html/* .
mv public_html/.htaccess . 2>/dev/null
rm -rf public_html/
fi
# Set proper permissions
chown -R www-data:www-data /var/www/html
find /var/www/html -type d -exec chmod 755 {} \;
find /var/www/html -type f -exec chmod 644 {} \;
Create Database and Import
# Access MySQL
mysql -u root -p
# Create database and user
CREATE DATABASE wordpress_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wordpress_user'@'localhost' IDENTIFIED BY 'strong_password_here';
GRANT ALL PRIVILEGES ON wordpress_db.* TO 'wordpress_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;
# Import your database backup
mysql -u root -p wordpress_db < /root/database-backup.sql
# Verify import
mysql -u root -p -e "USE wordpress_db; SHOW TABLES;"
Update wp-config.php
nano /var/www/html/wp-config.php
Update these lines:
define('DB_NAME', 'wordpress_db');
define('DB_USER', 'wordpress_user');
define('DB_PASSWORD', 'strong_password_here');
define('DB_HOST', 'localhost');
Update Site URLs
mysql -u wordpress_user -p wordpress_db
# Replace old-domain.com with your actual domain
UPDATE wp_options SET option_value = 'http://your-vps-ip' WHERE option_name = 'siteurl';
UPDATE wp_options SET option_value = 'http://your-vps-ip' WHERE option_name = 'home';
EXIT;
Test Your Site
Visit http://your_vps_ip in browser – your migrated site should load!
5. Optimize for High Performance
A. Optimize PHP Settings
# Find your PHP version
php -v
# Edit PHP configuration (adjust version number)
nano /etc/php/8.3/apache2/php.ini
Update these values:
memory_limit = 512M
max_execution_time = 300
max_input_time = 300
post_max_size = 128M
upload_max_filesize = 128M
max_input_vars = 3000
; OPcache - Critical for performance
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
opcache.enable_cli=0
B. Optimize Apache (MPM Prefork)
# Enable MPM Prefork
a2dismod mpm_event mpm_worker 2>/dev/null
a2enmod mpm_prefork
# Edit configuration
nano /etc/apache2/mods-available/mpm_prefork.conf
For 4GB RAM / 3 CPUs:
<IfModule mpm_prefork_module>
StartServers 5
MinSpareServers 5
MaxSpareServers 10
MaxRequestWorkers 150
MaxConnectionsPerChild 3000
</IfModule>
Calculation guide:
- Each Apache process ≈ 50-80MB
- MaxRequestWorkers = (Total RAM – 1GB for system) / 80MB
- For 4GB RAM: (4096 – 1024) / 80 = ~38 safe concurrent workers
- Set MaxRequestWorkers higher (150) for burst capacity
C. Enable Apache Performance Modules
# Enable essential modules
a2enmod deflate # Compression
a2enmod expires # Browser caching
a2enmod headers # HTTP headers control
a2enmod rewrite # URL rewriting
# Restart Apache
systemctl restart apache2
D. Configure Apache Virtual Host
nano /etc/apache2/sites-available/000-default.conf
Add inside <VirtualHost *:80>:
<VirtualHost *:80>
ServerName yourdomain.com
DocumentRoot /var/www/html
# Compression
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css
AddOutputFilterByType DEFLATE text/javascript application/javascript application/json
AddOutputFilterByType DEFLATE application/xml application/xhtml+xml
</IfModule>
# Browser caching
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpg "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType application/pdf "access plus 1 month"
ExpiresByType text/javascript "access plus 1 month"
</IfModule>
# Security headers
<IfModule mod_headers.c>
Header set X-Content-Type-Options "nosniff"
Header set X-Frame-Options "SAMEORIGIN"
Header set X-XSS-Protection "1; mode=block"
</IfModule>
# WordPress permalinks
<Directory /var/www/html>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
systemctl restart apache2
E. Optimize MySQL/MariaDB
nano /etc/mysql/mysql.conf.d/mysqld.cnf
Add in [mysqld] section:
For 4GB RAM VPS:
[mysqld]
# InnoDB Settings (50-60% of RAM for MySQL)
innodb_buffer_pool_size = 2G
innodb_log_file_size = 512M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT
innodb_file_per_table = 1
# Connection Settings
max_connections = 200
thread_cache_size = 50
table_open_cache = 4000
table_definition_cache = 2000
# Memory Tables
tmp_table_size = 128M
max_heap_table_size = 128M
# Query Optimization
join_buffer_size = 4M
sort_buffer_size = 4M
read_rnd_buffer_size = 2M
# Logging (disable in production for performance)
slow_query_log = 0
# slow_query_log_file = /var/log/mysql/mysql-slow.log
# long_query_time = 2
For 2GB RAM VPS:
innodb_buffer_pool_size = 1G
tmp_table_size = 64M
max_heap_table_size = 64M
# Restart MySQL
systemctl restart mysql
# Verify settings
mysql -u root -p -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"
F. WordPress-Specific Optimizations
Edit wp-config.php:
nano /var/www/html/wp-config.php
Add before /* That's all, stop editing! */:
// Memory limits
define('WP_MEMORY_LIMIT', '512M');
define('WP_MAX_MEMORY_LIMIT', '512M');
// Enable caching
define('WP_CACHE', true);
// Disable post revisions (or limit them)
define('WP_POST_REVISIONS', 3);
// Set autosave interval (in seconds)
define('AUTOSAVE_INTERVAL', 300);
// Disable file editing from dashboard
define('DISALLOW_FILE_EDIT', true);
// Increase timeout for external requests
define('WP_HTTP_BLOCK_EXTERNAL', false);
// Optimize database queries
define('WP_DEBUG', false);
define('WP_DEBUG_LOG', false);
define('WP_DEBUG_DISPLAY', false);
G. Install Redis Object Cache (Optional but Recommended)
# Install Redis
apt install redis-server php-redis -y
# Configure Redis
nano /etc/redis/redis.conf
Update these settings:
maxmemory 512mb
maxmemory-policy allkeys-lru
supervised systemd
# Start Redis
systemctl enable redis-server
systemctl start redis-server
# Restart PHP/Apache
systemctl restart apache2
Install Redis Object Cache Plugin:
- WordPress Admin → Plugins → Add New
- Search “Redis Object Cache”
- Install and Activate
- Settings → Redis → Enable Object Cache
H. Install Caching Plugin
Option 1: WP Super Cache (Free, Simple)
- Plugins → Add New → Search “WP Super Cache”
- Install & Activate
- Settings → WP Super Cache
- “Caching On” → Update Status
- Go to Advanced tab
- Enable: “Compress pages”, “Cache rebuild”
Option 2: W3 Total Cache (Free, Advanced)
- Plugins → Add New → Search “W3 Total Cache”
- Install & Activate
- Performance → General Settings
- Enable:
- Page Cache (Disk: Enhanced)
- Browser Cache
- Object Cache (Redis if installed)
- Performance → Page Cache → Cache front page
Option 3: WP Rocket (Paid, Best Performance)
- Premium plugin with one-click optimization
- Lazy loading, database cleanup, CDN integration
I. Image Optimization
Install Optimization Plugin:
- ShortPixel (Free tier available)
- Imagify (Free tier available)
- Smush (Free)
Bulk optimize existing images:
- Install plugin
- Go to plugin settings
- Run bulk optimization on Media Library
6. Point Domain to VPS
A. Update DNS Records
Log into Domain Registrar:
- Find DNS Management / Name Servers section
- Update A Records:
Type: A Name: @ Value: your_vps_ip TTL: 3600 - Add WWW subdomain:
Type: A Name: www Value: your_vps_ip TTL: 3600 - Save changes
DNS Propagation:
- Takes 1-48 hours (usually 1-4 hours)
- Check progress: https://dnschecker.org
B. Update WordPress URLs
Once DNS propagates:
mysql -u wordpress_user -p wordpress_db
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';
EXIT;
Or in WordPress Admin:
- Settings → General
- Update both URL fields
- Save Changes
C. Install SSL Certificate (Let’s Encrypt)
# Install Certbot
apt install certbot python3-certbot-apache -y
# Get SSL certificate
certbot --apache -d yourdomain.com -d www.yourdomain.com
# Follow prompts:
# - Enter email address
# - Agree to terms
# - Choose redirect HTTP to HTTPS: Yes (Option 2)
# Test auto-renewal
certbot renew --dry-run
SSL auto-renewal is configured automatically!
D. Force HTTPS in WordPress
Add to .htaccess (after # BEGIN WordPress):
# Force HTTPS
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
Or in wp-config.php:
define('FORCE_SSL_ADMIN', true);
if ($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')
$_SERVER['HTTPS']='on';
7. Final Steps & Monitoring
A. Clean Up
# Remove backup files from VPS
rm /root/database-backup.sql
rm /root/wordpress-backup.zip
# Clear any temporary files
apt autoremove -y
apt clean
B. Install Monitoring Tools
# Install htop for resource monitoring
apt install htop -y
# To use: just type 'htop' (F10 to exit)
# Install netdata for web-based monitoring (optional)
bash <(curl -Ss https://my-netdata.io/kickstart.sh)
# Access at: http://your_vps_ip:19999
C. Set Up Automated Backups
Install UpdraftPlus:
- Plugins → Add New → “UpdraftPlus”
- Install & Activate
- Settings → UpdraftPlus Backups
- Settings tab → Schedule backups
- Files: Weekly, Database: Daily
- Choose remote storage (Dropbox, Google Drive, etc.)
Or use VPS-level backups:
# Create backup script
nano /root/backup-wordpress.sh
#!/bin/bash
BACKUP_DIR="/root/backups"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p $BACKUP_DIR
# Backup files
tar -czf $BACKUP_DIR/wordpress_$DATE.tar.gz /var/www/html
# Backup database
mysqldump -u wordpress_user -pYOUR_PASSWORD wordpress_db > $BACKUP_DIR/db_$DATE.sql
# Keep only last 7 days
find $BACKUP_DIR -name "*.tar.gz" -mtime +7 -delete
find $BACKUP_DIR -name "*.sql" -mtime +7 -delete
echo "Backup completed: $DATE"
# Make executable
chmod +x /root/backup-wordpress.sh
# Add to crontab (daily at 2 AM)
crontab -e
Add line:
0 2 * * * /root/backup-wordpress.sh >> /var/log/wordpress-backup.log 2>&1
D. Security Hardening
Install Security Plugin:
- Wordfence Security (Free)
- Sucuri Security (Free)
- iThemes Security (Free)
Basic Security Measures:
- Change default admin username (not “admin”)
- Use strong passwords (20+ characters)
- Limit login attempts (via security plugin)
- Disable XML-RPC if not needed:
// Add to functions.php add_filter('xmlrpc_enabled', '__return_false'); - Hide WordPress version:
// Add to functions.php remove_action('wp_head', 'wp_generator'); - Configure firewall (UFW):
apt install ufw -y ufw allow 22/tcp # SSH ufw allow 80/tcp # HTTP ufw allow 443/tcp # HTTPS ufw enable ufw status
E. Performance Testing
Test your site speed:
- GTmetrix:https://gtmetrix.com
- Enter your domain
- Check Performance Score
- Review recommendations
- Google PageSpeed Insights:https://pagespeed.web.dev
- Test mobile and desktop
- Aim for 90+ score
- Load Testing:
# Install Apache Bench apt install apache2-utils -y # Test with 100 requests, 10 concurrent ab -n 100 -c 10 https://yourdomain.com/
Expected results after optimization:
- Page load time: < 2 seconds
- Time to First Byte (TTFB): < 600ms
- PageSpeed score: 85-95+
F. Monitor Resource Usage
# Check Apache processes
ps aux | grep apache2 | wc -l
# Check memory usage
free -h
# Check disk usage
df -h
# Check MySQL processes
mysqladmin -u root -p processlist
# Real-time monitoring
htop
Troubleshooting Common Issues
Issue 1: Site shows “Error establishing database connection”
Solution:
# Check MySQL is running
systemctl status mysql
# Verify database credentials in wp-config.php
nano /var/www/html/wp-config.php
# Test database connection
mysql -u wordpress_user -p wordpress_db
Issue 2: Apache won’t start after changes
Solution:
# Check Apache configuration
apache2ctl configtest
# View error logs
tail -f /var/log/apache2/error.log
# Restart Apache
systemctl restart apache2
Issue 3: 500 Internal Server Error
Solution:
# Check .htaccess file
nano /var/www/html/.htaccess
# Check PHP error logs
tail -f /var/log/apache2/error.log
# Check file permissions
ls -la /var/www/html/
Issue 4: Images not loading after migration
Solution:
# Fix file permissions
cd /var/www/html
find wp-content/uploads -type d -exec chmod 755 {} \;
find wp-content/uploads -type f -exec chmod 644 {} \;
chown -R www-data:www-data wp-content/uploads
# Update image URLs in database
mysql -u wordpress_user -p wordpress_db
UPDATE wp_posts SET post_content = REPLACE(post_content, 'old-domain.com', 'new-domain.com');
UPDATE wp_postmeta SET meta_value = REPLACE(meta_value, 'old-domain.com', 'new-domain.com');
Issue 5: Slow site after migration
Solution:
- Clear all caches (caching plugin + browser)
- Regenerate thumbnails (use plugin)
- Check MySQL slow query log
- Disable plugins one by one to find culprit
- Run database optimization:
mysql -u wordpress_user -p wordpress_db OPTIMIZE TABLE wp_posts; OPTIMIZE TABLE wp_postmeta; OPTIMIZE TABLE wp_options;
Performance Benchmarks
Before Optimization (Typical Shared Hosting):
- Page Load: 4-8 seconds
- TTFB: 1-3 seconds
- Concurrent users: 10-20
- PageSpeed Score: 40-60
After Optimization (Optimized VPS):
- Page Load: 1-2 seconds
- TTFB: 200-600ms
- Concurrent users: 100-500+
- PageSpeed Score: 85-95+
Resource Usage (4GB VPS):
- Idle: 500MB RAM, 5% CPU
- Medium Load (50 users): 1.5GB RAM, 30% CPU
- High Load (200 users): 3GB RAM, 70% CPU

