# Check current server load — anything above the CPU count is bad
uptime
# Output: load average: 24.31, 22.67, 21.89 (on a 4-core box... yikes) # See how many sites are running on this box
ls /home/ | wc -l
# Output: 187 # Check disk I/O wait — high iowait means disk contention
iostat -x 1 3
# Look at %iowait and await columns
# Check current server load — anything above the CPU count is bad
uptime
# Output: load average: 24.31, 22.67, 21.89 (on a 4-core box... yikes) # See how many sites are running on this box
ls /home/ | wc -l
# Output: 187 # Check disk I/O wait — high iowait means disk contention
iostat -x 1 3
# Look at %iowait and await columns
# Check current server load — anything above the CPU count is bad
uptime
# Output: load average: 24.31, 22.67, 21.89 (on a 4-core box... yikes) # See how many sites are running on this box
ls /home/ | wc -l
# Output: 187 # Check disk I/O wait — high iowait means disk contention
iostat -x 1 3
# Look at %iowait and await columns
# First things first — -weight: 500;">update and lock it down
-weight: 500;">apt -weight: 500;">update && -weight: 500;">apt -weight: 500;">upgrade -y # Create a non-root user
adduser deploy
usermod -aG -weight: 600;">sudo deploy # Set up SSH key auth and -weight: 500;">disable password login
mkdir -p /home/deploy/.ssh
cp ~/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys # Disable root login and password auth
sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
-weight: 500;">systemctl -weight: 500;">restart sshd # Basic firewall — only allow SSH, HTTP, HTTPS
ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw -weight: 500;">enable
# First things first — -weight: 500;">update and lock it down
-weight: 500;">apt -weight: 500;">update && -weight: 500;">apt -weight: 500;">upgrade -y # Create a non-root user
adduser deploy
usermod -aG -weight: 600;">sudo deploy # Set up SSH key auth and -weight: 500;">disable password login
mkdir -p /home/deploy/.ssh
cp ~/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys # Disable root login and password auth
sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
-weight: 500;">systemctl -weight: 500;">restart sshd # Basic firewall — only allow SSH, HTTP, HTTPS
ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw -weight: 500;">enable
# First things first — -weight: 500;">update and lock it down
-weight: 500;">apt -weight: 500;">update && -weight: 500;">apt -weight: 500;">upgrade -y # Create a non-root user
adduser deploy
usermod -aG -weight: 600;">sudo deploy # Set up SSH key auth and -weight: 500;">disable password login
mkdir -p /home/deploy/.ssh
cp ~/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys # Disable root login and password auth
sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
-weight: 500;">systemctl -weight: 500;">restart sshd # Basic firewall — only allow SSH, HTTP, HTTPS
ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw -weight: 500;">enable
# Install the essentials
-weight: 500;">apt -weight: 500;">install -y nginx mariadb-server php8.3-fpm php8.3-mysql \ php8.3--weight: 500;">curl php8.3-gd php8.3-mbstring php8.3-xml php8.3-zip # Secure MariaDB
mysql_secure_installation # Tune PHP-FPM for your available memory
# For 1GB RAM, these are reasonable starting values
-weight: 600;">sudo nano /etc/php/8.3/fpm/pool.d/www.conf
# Install the essentials
-weight: 500;">apt -weight: 500;">install -y nginx mariadb-server php8.3-fpm php8.3-mysql \ php8.3--weight: 500;">curl php8.3-gd php8.3-mbstring php8.3-xml php8.3-zip # Secure MariaDB
mysql_secure_installation # Tune PHP-FPM for your available memory
# For 1GB RAM, these are reasonable starting values
-weight: 600;">sudo nano /etc/php/8.3/fpm/pool.d/www.conf
# Install the essentials
-weight: 500;">apt -weight: 500;">install -y nginx mariadb-server php8.3-fpm php8.3-mysql \ php8.3--weight: 500;">curl php8.3-gd php8.3-mbstring php8.3-xml php8.3-zip # Secure MariaDB
mysql_secure_installation # Tune PHP-FPM for your available memory
# For 1GB RAM, these are reasonable starting values
-weight: 600;">sudo nano /etc/php/8.3/fpm/pool.d/www.conf
; Switch from dynamic to ondemand if memory is tight
pm = ondemand
pm.max_children = 10
pm.process_idle_timeout = 10s
pm.max_requests = 500 ; Enable opcache — this alone cut response times in half
[opcache]
opcache.-weight: 500;">enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.validate_timestamps=0 ; set to 1 during development
; Switch from dynamic to ondemand if memory is tight
pm = ondemand
pm.max_children = 10
pm.process_idle_timeout = 10s
pm.max_requests = 500 ; Enable opcache — this alone cut response times in half
[opcache]
opcache.-weight: 500;">enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.validate_timestamps=0 ; set to 1 during development
; Switch from dynamic to ondemand if memory is tight
pm = ondemand
pm.max_children = 10
pm.process_idle_timeout = 10s
pm.max_requests = 500 ; Enable opcache — this alone cut response times in half
[opcache]
opcache.-weight: 500;">enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.validate_timestamps=0 ; set to 1 during development
# On the old server — dump the database
mysqldump -u root -p --all-databases --single-transaction > dump.sql # Tar up the site files
tar czf site-backup.tar.gz /var/www/html/ # Transfer to new server
rsync -avz --progress dump.sql deploy@new-server:/tmp/
rsync -avz --progress site-backup.tar.gz deploy@new-server:/tmp/ # On the new server — import
mysql -u root -p < /tmp/dump.sql
tar xzf /tmp/site-backup.tar.gz -C /
# On the old server — dump the database
mysqldump -u root -p --all-databases --single-transaction > dump.sql # Tar up the site files
tar czf site-backup.tar.gz /var/www/html/ # Transfer to new server
rsync -avz --progress dump.sql deploy@new-server:/tmp/
rsync -avz --progress site-backup.tar.gz deploy@new-server:/tmp/ # On the new server — import
mysql -u root -p < /tmp/dump.sql
tar xzf /tmp/site-backup.tar.gz -C /
# On the old server — dump the database
mysqldump -u root -p --all-databases --single-transaction > dump.sql # Tar up the site files
tar czf site-backup.tar.gz /var/www/html/ # Transfer to new server
rsync -avz --progress dump.sql deploy@new-server:/tmp/
rsync -avz --progress site-backup.tar.gz deploy@new-server:/tmp/ # On the new server — import
mysql -u root -p < /tmp/dump.sql
tar xzf /tmp/site-backup.tar.gz -C /
server { listen 80; server_name example.com www.example.com; root /var/www/html; index index.php; # Enable gzip — shared hosts often have this disabled gzip on; gzip_types text/css application/javascript application/json image/svg+xml; gzip_min_length 1000; # Static file caching location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ { expires 30d; add_header Cache-Control "public, immutable"; } location / { try_files $uri $uri/ /index.php?$args; } location ~ \.php$ { fastcgi_pass unix:/run/php/php8.3-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; fastcgi_read_timeout 60; }
}
server { listen 80; server_name example.com www.example.com; root /var/www/html; index index.php; # Enable gzip — shared hosts often have this disabled gzip on; gzip_types text/css application/javascript application/json image/svg+xml; gzip_min_length 1000; # Static file caching location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ { expires 30d; add_header Cache-Control "public, immutable"; } location / { try_files $uri $uri/ /index.php?$args; } location ~ \.php$ { fastcgi_pass unix:/run/php/php8.3-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; fastcgi_read_timeout 60; }
}
server { listen 80; server_name example.com www.example.com; root /var/www/html; index index.php; # Enable gzip — shared hosts often have this disabled gzip on; gzip_types text/css application/javascript application/json image/svg+xml; gzip_min_length 1000; # Static file caching location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ { expires 30d; add_header Cache-Control "public, immutable"; } location / { try_files $uri $uri/ /index.php?$args; } location ~ \.php$ { fastcgi_pass unix:/run/php/php8.3-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; fastcgi_read_timeout 60; }
}
# Install certbot and grab a certificate
-weight: 500;">apt -weight: 500;">install -y certbot python3-certbot-nginx
certbot --nginx -d example.com -d www.example.com # Verify auto-renewal works
certbot renew --dry-run
# Install certbot and grab a certificate
-weight: 500;">apt -weight: 500;">install -y certbot python3-certbot-nginx
certbot --nginx -d example.com -d www.example.com # Verify auto-renewal works
certbot renew --dry-run
# Install certbot and grab a certificate
-weight: 500;">apt -weight: 500;">install -y certbot python3-certbot-nginx
certbot --nginx -d example.com -d www.example.com # Verify auto-renewal works
certbot renew --dry-run
# Measure TTFB
-weight: 500;">curl -o /dev/null -s -w "TTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" https://example.com # Before (shared hosting):
# TTFB: 2.847s
# Total: 3.221s # After (VPS):
# TTFB: 0.183s # Total: 0.247s
# Measure TTFB
-weight: 500;">curl -o /dev/null -s -w "TTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" https://example.com # Before (shared hosting):
# TTFB: 2.847s
# Total: 3.221s # After (VPS):
# TTFB: 0.183s # Total: 0.247s
# Measure TTFB
-weight: 500;">curl -o /dev/null -s -w "TTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" https://example.com # Before (shared hosting):
# TTFB: 2.847s
# Total: 3.221s # After (VPS):
# TTFB: 0.183s # Total: 0.247s - CPU throttling: Your process gets timesliced with everyone else. During peak hours, your PHP workers are literally waiting in line.
- Disk I/O contention: One site doing heavy database writes tanks read performance for everyone. Shared disks are the bottleneck nobody talks about.
- Memory limits: You're typically capped at 256-512MB regardless of what the server actually has. OOM kills happen silently.
- Noisy neighbors: You have zero control over what other tenants are doing. One misconfigured cron job can spike load for the entire box. - Set up unattended security updates: -weight: 500;">apt -weight: 500;">install unattended-upgrades and configure it. Seriously, do this day one.
- Monitor disk space: Logs and backups will fill your disk eventually. Set up a cron job or use a monitoring tool to alert you.
- Automate backups: A VPS without backups is a ticking time bomb. Schedule daily database dumps and weekly full snapshots.
- Watch your logs: Check /var/log/nginx/error.log and PHP-FPM logs periodically. Errors that were invisible on shared hosting will now show up clearly.