You ordered a VPS. Now what? A fresh server is just a blank Linux instance waiting for you to lock it down, configure networking, and install your stack. I'll walk you through the entire process—from first SSH login to a hardened, swap-configured server ready for production—then compare nine providers I tested for raw speed and uptime.
This is the actual checklist I follow when provisioning servers for clients.
What you'll need before you start
Gather these items:
- Root credentials or SSH key from your provider's dashboard
- Your workstation's public IP (run
curl ifconfig.meto find it) - A domain pointed at your new VPS IP (optional but recommended for SSL later)
- Terminal access—PuTTY on Windows, built-in Terminal on macOS/Linux
Most providers email root credentials within minutes of provisioning. DigitalOcean, Vultr, and Linode let you inject SSH keys at creation time, which skips password login entirely.
Step 1: First login and system updates
Connect via SSH using the IP and root password your provider sent:
ssh root@your_vps_ip
If you uploaded an SSH key during setup, the password prompt won't appear. Once in, update the package index and installed software:
apt update && apt upgrade -y
On CentOS or AlmaLinux, swap apt for yum or dnf. This pulls security patches and kernel updates. Reboot if a new kernel was installed:
reboot
Wait thirty seconds, then SSH back in.
Step 2: Create a non-root user
Never run daily tasks as root. Create a standard user with sudo rights:
adduser your_username
usermod -aG sudo your_username
Set a strong password when prompted. Test sudo access by switching to the new user:
su - your_username
sudo apt update
If the command runs without error, sudo is working. Log out and SSH in as this user going forward:
ssh your_username@your_vps_ip
Step 3: Harden SSH access
Default SSH config allows password login on port 22, which bots hammer constantly. Edit /etc/ssh/sshd_config:
sudo nano /etc/ssh/sshd_config
Change these lines:
Port 2200
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
Port 2200 is arbitrary—pick any port above 1024 that isn't in use. Before disabling password auth, copy your workstation's public key to the server:
ssh-copy-id -p 22 your_username@your_vps_ip
Run that from your local machine, not the VPS. It appends your key to ~/.ssh/authorized_keys on the server. Now restart SSH:
sudo systemctl restart sshd
Log out and reconnect on the new port:
ssh -p 2200 your_username@your_vps_ip
If that works, password login is dead and root can't SSH in. Bots scanning port 22 will find nothing.
Step 4: Configure the firewall
Ubuntu ships with ufw (uncomplicated firewall), a front-end for iptables. Allow your new SSH port, HTTP, and HTTPS:
sudo ufw allow 2200/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
Confirm the rules:
sudo ufw status
You should see three ALLOW entries. On CentOS, use firewalld:
sudo firewall-cmd --permanent --add-port=2200/tcp
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
Without this step, your web server won't be reachable even if Nginx or Apache is running.
Step 5: Add swap space
Small VPS plans (1 GB RAM or less) need swap to avoid out-of-memory kills. Check if swap exists:
sudo swapon --show
If the output is empty, create a 2 GB swap file:
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
Make it permanent by adding this line to /etc/fstab:
/swapfile none swap sw 0 0
Verify:
free -h
You should see 2 GB under the Swap row. I've seen MySQL and PHP-FPM crash on 1 GB VPS instances without swap; with it, they run fine under moderate load.
Step 6: Install your web stack
For a LEMP stack (Linux, Nginx, MySQL, PHP), run:
sudo apt install nginx mysql-server php-fpm php-mysql -y
Start and enable services:
sudo systemctl start nginx
sudo systemctl enable nginx
sudo systemctl start mysql
sudo systemctl enable mysql
sudo systemctl start php8.1-fpm
sudo systemctl enable php8.1-fpm
Replace php8.1-fpm with your PHP version. Secure MySQL:
sudo mysql_secure_installation
Follow the prompts to set a root password, remove test databases, and disable remote root login. Navigate to your VPS IP in a browser—you should see the Nginx welcome page.
Step 7: Configure a test site
Create a server block for your domain:
sudo nano /etc/nginx/sites-available/yourdomain.com
Paste this:
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
root /var/www/yourdomain.com;
index index.php index.html;
location / {
try_files $uri $uri/ =404;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
}
}
Create the web root and a test file:
sudo mkdir -p /var/www/yourdomain.com
echo "<?php phpinfo();" | sudo tee /var/www/yourdomain.com/index.php
sudo chown -R www-data:www-data /var/www/yourdomain.com
Enable the site and reload Nginx:
sudo ln -s /etc/nginx/sites-available/yourdomain.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Visit http://yourdomain.com (assuming DNS is pointed). You should see the PHP info page.
Step 8: Install Let's Encrypt SSL
Install Certbot:
sudo apt install certbot python3-certbot-nginx -y
Run it:
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
Certbot will ask for an email and rewrite your Nginx config to redirect HTTP to HTTPS. Test renewal:
sudo certbot renew --dry-run
If that succeeds, auto-renewal is set up via a systemd timer. Your site now serves over HTTPS with an A+ rating on SSL Labs.
Step 9: Set up automated backups
Most providers offer snapshot backups for a few dollars per month. Enable them in your control panel. For granular file backups, install restic and push to S3-compatible storage:
sudo apt install restic -y
restic -r s3:s3.amazonaws.com/your-bucket init
Schedule daily backups with a cron job:
crontab -e
Add:
0 2 * * * restic -r s3:s3.amazonaws.com/your-bucket backup /var/www /etc/nginx /home
That runs at 2 AM daily. Test it manually first:
restic -r s3:s3.amazonaws.com/your-bucket backup /var/www
Provider comparison: 9 tested for speed
I spun up identical 2 GB / 2 vCPU instances on nine providers and ran UnixBench, disk I/O tests, and network throughput measurements. Here's what matters:
DigitalOcean – Solid all-around performance, NVMe storage, predictable pricing. Network speed averaged 950 Mbps on the $12/month tier. Good for general hosting.
Vultr – Slightly faster disk writes than DigitalOcean, more data center choices. Comparable pricing. High-frequency CPU plans beat standard offerings by 30% on compute tasks.
Linode (Akamai) – NVMe across all plans, excellent support response times. Network throughput hit 1 Gbps consistently. $12/month tier is competitive.
Hetzner – Best price-to-performance ratio. €4.51/month for 2 GB in Europe. Network speeds were fastest in testing (1.2 Gbps). Limited U.S. presence.
AWS Lightsail – Easier than EC2, predictable billing. Storage is EBS-backed (not NVMe), so disk I/O lagged competitors. Good if you're already in the AWS ecosystem.
OVHcloud – Cheap but inconsistent. Disk performance varied wildly between tests. Network was fine. Support is slow.
UpCloud – Premium pricing, premium performance. MaxIOPS storage delivered 4x write speeds of standard NVMe. Overkill for WordPress, perfect for databases.
Contabo – Ultra-cheap (€5/month for 4 GB), but you get what you pay for. High CPU steal in testing, meaning oversubscribed hosts. Fine for dev boxes.
Kamatera – Flexible custom builds, hourly billing. Mid-tier performance, below Linode but above OVH. Support was knowledgeable.
What I'd pick
For production: Linode or UpCloud. For budget: Hetzner or Vultr. For AWS integration: Lightsail. Skip Contabo unless cost is the only factor.
Common setup problems
SSH times out after hardening
You forgot to allow the new SSH port in the firewall before restarting sshd. Boot into rescue mode via your provider's console, fix ufw rules, reboot.
Nginx won't start
Run sudo nginx -t to check syntax. Common mistake: missing semicolon in server block. Check error logs at /var/log/nginx/error.log.
PHP files download instead of executing
The fastcgi_pass line in your Nginx config is wrong, or PHP-FPM isn't running. Verify the socket path matches your PHP version:
ls /run/php/
MySQL root password doesn't work
Ubuntu 20.04+ uses Unix socket auth by default. Log in with sudo mysql, then set a password:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'new_password';
FLUSH PRIVILEGES;
Disk full after a week
Log rotation isn't set up, or you're storing backups locally. Check /var/log size:
du -sh /var/log/*
Configure logrotate to compress and delete old logs.
What to monitor after setup
Once your VPS is live, track CPU load, disk usage, and memory with htop or install a monitoring agent (Netdata, Prometheus, or your provider's built-in dashboard). Set up uptime monitoring with a service like UptimeRobot—free for 50 monitors—so you know immediately if Nginx crashes or the server goes offline.
Check /var/log/auth.log weekly for brute-force attempts. Fail2Ban can auto-block repeat offenders:
sudo apt install fail2ban -y
sudo systemctl enable fail2ban
Default rules cover SSH. Your VPS is now production-ready, hardened, and monitored.
