You have just purchased a VPS. You have a root password and an IP address. Now what?
The gap between a freshly provisioned VPS and a production-ready server is enormous. An unconfigured VPS is a target - within minutes of going online, automated bots will begin probing for default credentials, open ports, and known vulnerabilities. According to research, the average time for a new server to receive its first attack is under 10 minutes.
This guide closes that gap. We will take a bare VPS from zero to a fully hardened, monitored, backed-up, production-ready web server - step by step, command by command. Every section applies to Ubuntu 24.04 LTS, Debian 12, AlmaLinux 9, and Rocky Linux 9, with distro-specific commands clearly labeled.
By the end, you will have a server that is secure enough for production workloads, performant enough to handle real traffic, and maintainable enough to run for years without surprises.
Part 1: Choosing Your VPS Provider and Plan
Before touching a terminal, the first decision is where and what to provision. The VPS market in 2026 offers excellent options at every price point.
Provider Comparison
For most use cases, these are the top choices:
- Hetzner (.50/mo) - Best price-to-performance ratio, especially in Europe. AMD EPYC CPUs, NVMe storage, excellent network. EU data centers (Germany, Finland) plus US (Ashburn, Hillsboro).
- DigitalOcean (/mo) - The developer-friendly choice. Exceptional documentation, simple UI, managed databases and Kubernetes available. Good for startups.
- Linode/Akamai (/mo) - Reliable with great support. Now part of Akamai, offering CDN integration. Global data centers.
- Vultr (/mo) - 32 worldwide locations, bare metal options, and high-frequency compute. Good for global reach.
- OVHcloud (.50/mo) - Cheapest EU option with built-in anti-DDoS. Good for GDPR compliance and EU data sovereignty.
- Contabo (.50/mo) - Maximum resources for the price (8GB RAM, 200GB SSD for 2). Slower I/O than competitors but unbeatable specs per dollar for non-latency-critical workloads.
Sizing Your Server
Match your plan to your workload:
- Personal blog / portfolio: 1 vCPU, 1GB RAM, 20GB SSD (-6/mo)
- WordPress / small CMS: 2 vCPU, 2GB RAM, 40GB SSD (-12/mo)
- Web app + database: 2 vCPU, 4GB RAM, 80GB SSD (5-24/mo)
- E-commerce / SaaS: 4 vCPU, 8GB RAM, 160GB NVMe (0-48/mo)
- High-traffic production: 8 vCPU, 16GB RAM, 320GB NVMe (0-96/mo)
Start small and scale vertically when you have actual usage data. Most providers allow live upgrades without downtime.
OS Selection
Ubuntu 24.04 LTS is recommended for most users - it has the largest community, best documentation, and widest software support. Debian 12 is excellent for minimal, stable servers. AlmaLinux 9 or Rocky Linux 9 are the choice for enterprise environments needing RHEL compatibility and 10-year support cycles.
Part 2: Initial System Configuration
The moment your VPS is provisioned, the clock starts ticking. Automated scanners will find your IP address within minutes. The first session should focus entirely on securing access and establishing a baseline configuration.
Step 1: Connect and Update
# SSH into your new server
ssh root@YOUR_SERVER_IP
# Update everything immediately
# Ubuntu/Debian:
apt update && apt upgrade -y
# AlmaLinux/Rocky:
dnf update -y
# Reboot if kernel was updated
reboot
Step 2: Set Hostname and Timezone
hostnamectl set-hostname web01.example.com
timedatectl set-timezone Europe/Budapest # Your timezone
timedatectl set-ntp true # Automatic time sync
Step 3: Create Admin User
Never work as root. Create a regular user with sudo privileges:
# Ubuntu/Debian:
adduser admin
usermod -aG sudo admin
# AlmaLinux/Rocky:
useradd -m -s /bin/bash admin
passwd admin
usermod -aG wheel admin
Step 4: Transfer SSH Key
# Copy your SSH key to the new user
mkdir -p /home/admin/.ssh
cp /root/.ssh/authorized_keys /home/admin/.ssh/
chown -R admin:admin /home/admin/.ssh
chmod 700 /home/admin/.ssh
chmod 600 /home/admin/.ssh/authorized_keys
Critical: Test SSH login as the new admin user in a separate terminal window before proceeding. If you lock yourself out, you will need console access from your provider dashboard.
Step 5: Install Essential Packages
# Ubuntu/Debian:
apt install -y vim curl wget htop git unzip tar tmux ufw fail2ban net-tools
# AlmaLinux/Rocky:
dnf install -y epel-release
dnf install -y vim curl wget htop git unzip tar tmux fail2ban
Part 3: SSH Hardening
SSH is the front door to your server. A misconfigured SSH service is the most common entry point for attackers. We will lock it down completely.
Generate a Strong Key Pair (Local Machine)
# Ed25519 is the modern standard - faster and more secure than RSA
ssh-keygen -t ed25519 -C "admin@example.com"
# Copy to server
ssh-copy-id -i ~/.ssh/id_ed25519.pub admin@YOUR_SERVER_IP
Harden sshd_config
Edit /etc/ssh/sshd_config and apply these security settings:
Port 2222 # Non-standard port (reduces 99% of automated scans)
PermitRootLogin no # Never allow root SSH
PasswordAuthentication no # Key-only (most important setting)
PubkeyAuthentication yes
MaxAuthTries 3 # Lock after 3 failures
ClientAliveInterval 300 # 5-minute idle timeout
ClientAliveCountMax 2 # Disconnect after 2 missed keepalives
AllowUsers admin deploy # Whitelist specific users
X11Forwarding no # Not needed on servers
AllowTcpForwarding no # Unless you need SSH tunnels
Before restarting SSH, update the firewall to allow the new port:
# UFW (Ubuntu/Debian):
ufw allow 2222/tcp
ufw delete allow 22/tcp
# firewalld (AlmaLinux/Rocky):
firewall-cmd --add-port=2222/tcp --permanent
firewall-cmd --remove-service=ssh --permanent
firewall-cmd --reload
# Test config and restart
sshd -t # Verify syntax
systemctl restart sshd # Apply changes
Fail2Ban Setup
Fail2Ban monitors log files and automatically bans IPs that show brute-force behavior:
# Create /etc/fail2ban/jail.local
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
[sshd]
enabled = true
port = 2222
logpath = /var/log/auth.log # Ubuntu
# logpath = /var/log/secure # AlmaLinux
# Enable and start
systemctl enable --now fail2ban
fail2ban-client status sshd # Verify it is running
Part 4: Firewall Configuration
The firewall is your first line of defense. It blocks 99% of automated attacks before they reach any service.
The Golden Rule: Default Deny
Block everything, then allow only what you need. For a typical web server, you need exactly three inbound ports: SSH (custom port), HTTP (80), and HTTPS (443). Nothing else.
UFW Setup (Ubuntu/Debian)
ufw default deny incoming # Block all incoming
ufw default allow outgoing # Allow all outgoing
ufw allow 2222/tcp # SSH (custom port)
ufw allow 80/tcp # HTTP
ufw allow 443/tcp # HTTPS
ufw enable # Activate the firewall
ufw status verbose # Verify rules
firewalld Setup (AlmaLinux/Rocky)
systemctl enable --now firewalld
firewall-cmd --add-port=2222/tcp --permanent
firewall-cmd --add-service=http --permanent
firewall-cmd --add-service=https --permanent
firewall-cmd --remove-service=ssh --permanent # Remove default SSH
firewall-cmd --reload
Never expose database ports (3306, 5432) to the public internet. If your application and database are on the same server, the database should listen only on 127.0.0.1. If they are on separate servers, use a private network or SSH tunnel.
Part 5: Nginx Web Server
Nginx is the web server of choice for modern production deployments. It handles SSL termination, reverse proxying, static file serving, rate limiting, and load balancing.
Installation
# Ubuntu/Debian:
apt install nginx -y
# AlmaLinux/Rocky:
dnf install nginx -y
setsebool -P httpd_can_network_connect on # SELinux: allow proxy
# Both:
systemctl enable --now nginx
Server Block Configuration
Create your site config at /etc/nginx/sites-available/example.com (Ubuntu) or /etc/nginx/conf.d/example.com.conf (AlmaLinux):
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com/public;
index index.php index.html;
location / {
try_files / /index.php?;
}
# PHP-FPM
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME ;
include fastcgi_params;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Deny access to hidden files
location ~ /\. { deny all; }
}
For Node.js or Python applications, replace the PHP-FPM block with a reverse proxy:
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade ;
proxy_set_header Connection "upgrade";
proxy_set_header Host ;
proxy_set_header X-Real-IP ;
proxy_set_header X-Forwarded-For ;
proxy_set_header X-Forwarded-Proto ;
}
Always test configuration before reloading: nginx -t && systemctl reload nginx
Part 6: SSL/TLS with Let us Encrypt
Every production server needs HTTPS. Let us Encrypt provides free, automated SSL certificates through Certbot.
Installation and Certificate Setup
# Ubuntu/Debian:
apt install certbot python3-certbot-nginx -y
# AlmaLinux/Rocky:
dnf install epel-release certbot python3-certbot-nginx -y
# Obtain and auto-configure SSL
certbot --nginx -d example.com -d www.example.com
# Test auto-renewal
certbot renew --dry-run
Certbot automatically modifies your Nginx config to include SSL settings and creates a redirect from HTTP to HTTPS. For the highest security grade, add these settings to your SSL server block:
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_stapling on;
ssl_stapling_verify on;
Set up automatic renewal with a cron job: 0 3 * * * certbot renew --quiet --post-hook "systemctl reload nginx"
Test your SSL configuration at SSL Labs - aim for an A+ grade. Enable HSTS only after confirming HTTPS works perfectly, as it instructs browsers to never use HTTP again for your domain.
Part 7: Database Setup
Most web applications need a database. PostgreSQL and MariaDB are the two most popular choices on Linux servers.
PostgreSQL
# Install
apt install postgresql -y # Ubuntu
dnf install postgresql-server -y # AlmaLinux
postgresql-setup --initdb # AlmaLinux only
systemctl enable --now postgresql
# Create user and database
sudo -u postgres psql
CREATE USER app WITH PASSWORD 'your_secure_password';
CREATE DATABASE mydb OWNER app;
GRANT ALL ON DATABASE mydb TO app;
\q
MariaDB
# Install
apt install mariadb-server -y # Ubuntu
dnf install mariadb-server -y # AlmaLinux
systemctl enable --now mariadb
mysql_secure_installation # Run the security wizard
Database Security Essentials
- Bind to
localhostonly (listen_addresses = 'localhost'in postgresql.conf,bind-address = 127.0.0.1in my.cnf) - Use strong authentication methods (scram-sha-256 for PostgreSQL, caching_sha2_password for MariaDB)
- Never expose database ports to the public internet
- Set up automated daily backups with tested restore procedures
- Enable slow query logging to identify performance bottlenecks
Part 8: Application Runtime Setup
With Nginx, SSL, and a database in place, the next step is your application runtime.
PHP-FPM
# Ubuntu:
apt install php8.3-fpm php8.3-cli php8.3-mbstring php8.3-xml php8.3-pgsql php8.3-curl php8.3-gd -y
# AlmaLinux:
dnf module enable php:8.2
dnf install php-fpm php-cli php-mbstring php-xml php-pgsql php-curl php-gd -y
systemctl enable --now php-fpm
Node.js with PM2
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt install nodejs -y
npm install -g pm2
pm2 start app.js --name myapp
pm2 startup systemd # Auto-start on reboot
pm2 save # Save process list
Python with Gunicorn
apt install python3 python3-pip python3-venv -y
python3 -m venv /opt/myapp/venv
source /opt/myapp/venv/bin/activate
pip install -r requirements.txt gunicorn
gunicorn --bind 127.0.0.1:8000 app:app
Always run application processes behind Nginx reverse proxy. Never expose Node.js, Gunicorn, or PHP-FPM directly to the internet. Nginx handles SSL termination, rate limiting, buffering, and static file serving far more efficiently.
Part 9: DNS and Domain Configuration
With your server running, point your domain to it. At minimum, you need these DNS records:
- A record:
example.compointing to your server IPv4 address - A record:
www.example.compointing to the same IP (or CNAME to apex) - AAAA record: If your server has IPv6
- CAA record:
0 issue "letsencrypt.org"to restrict certificate issuance
Email Authentication (Even If You Do Not Send Email)
Set up SPF, DKIM, and DMARC to prevent your domain from being spoofed for spam:
- SPF:
v=spf1 mx -all(orv=spf1 -allif you never send email) - DMARC:
v=DMARC1; p=reject; rua=mailto:dmarc@example.com
These TXT records protect your domain reputation even if you only receive email.
Cloudflare (Optional but Recommended)
Cloudflare provides free DNS hosting, DDoS protection, WAF, and CDN. Set SSL mode to "Full (Strict)" when you have a valid origin certificate from Let us Encrypt.
Part 10: Monitoring and Log Management
A server without monitoring is a server waiting to fail silently. At minimum, you need to track disk usage, memory, CPU load, and service health.
Essential Monitoring Commands
htop # Interactive process viewer
free -h # Memory usage
df -hT # Disk usage with filesystem types
uptime # Load averages
ss -tulnp # Listening ports
journalctl -u nginx -f # Follow service logs
tail -f /var/log/auth.log # Monitor SSH attempts
Log Analysis One-Liners
# Top 10 IPs hitting your server
awk '{print }' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10
# Count 500 errors today
grep "07/Mar/2026" /var/log/nginx/access.log | grep " 500 " | wc -l
# Recent failed SSH attempts
grep "Failed password" /var/log/auth.log | tail -20
# Most requested 404 URLs
grep " 404 " /var/log/nginx/access.log | awk '{print }' | sort | uniq -c | sort -rn | head -10
Monitoring Solutions
- Cockpit (built-in on AlmaLinux, easy to install on Ubuntu) - Web-based management and monitoring on port 9090
- Netdata - Real-time per-server monitoring with zero configuration
- UptimeRobot / Better Uptime - External monitoring that alerts you when your site goes down
- Prometheus + Grafana - The industry-standard stack for multi-server metric collection and dashboarding
At an absolute minimum, create a simple health check script that runs every 5 minutes via cron and sends an email when disk usage exceeds 85% or memory usage exceeds 90%.
Part 11: Automated Backups
Follow the 3-2-1 rule: 3 copies of your data, on 2 different media types, with 1 copy offsite.
Backup Script
#!/bin/bash
set -euo pipefail
DATE=20260307-1123
BACKUP_DIR="/backup/"
mkdir -p
# Database
pg_dump -U postgres mydb | gzip > /db.sql.gz
# Web files
tar czf /www.tar.gz /var/www/
# Config files
tar czf /etc.tar.gz /etc/nginx /etc/ssh /etc/letsencrypt
# Upload to offsite storage
aws s3 sync s3://my-backups// --storage-class STANDARD_IA
# Cleanup local backups older than 7 days
find /backup -maxdepth 1 -type d -mtime +7 -exec rm -rf {} \;
echo "[Sat Mar 7 11:23:44 AM UTC 2026] Backup completed" >> /var/log/backup.log
Schedule via cron: 0 3 * * * /opt/scripts/backup.sh
For offsite storage, Backblaze B2 (/nix/store/smkzrg2vvp3lng3hq7v9svfni5mnqjh2-bash-interactive-5.2p37/bin/bash.006/GB/month) offers the best value. Hetzner Storage Box is excellent for EU data residency.
The most important backup practice: test your restores monthly. Actually restore your database and files to a test server to verify the entire process works. An untested backup is not a backup.
Part 12: Security Hardening
Beyond SSH and firewall, production servers need additional hardening layers.
Kernel Hardening (sysctl)
Add these settings to /etc/sysctl.d/99-hardening.conf:
# SYN flood protection
net.ipv4.tcp_syncookies = 1
# Prevent source routing
net.ipv4.conf.all.accept_source_route = 0
# Anti-spoofing
net.ipv4.conf.all.rp_filter = 1
# Ignore ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
# Ignore broadcast pings
net.ipv4.icmp_echo_ignore_broadcasts = 1
# Log suspicious packets
net.ipv4.conf.all.log_martians = 1
Apply with sysctl -p /etc/sysctl.d/99-hardening.conf
Security Scanning
- Lynis - Comprehensive security audit:
lynis audit system - rkhunter / chkrootkit - Rootkit detection
- AIDE - File integrity monitoring to detect unauthorized changes
Additional Hardening
- Enable automatic security updates (
unattended-upgradeson Ubuntu,dnf-automaticon AlmaLinux) - Disable unused services:
systemctl disable --now cups bluetooth avahi-daemon - Set up the Linux Audit System (
auditd) to track security-relevant events - Review SUID files regularly:
find / -perm -4000 -type f - Add a legal warning banner to
/etc/issuefor compliance
Part 13: Deployment Workflow
A proper deployment process prevents the "I will just edit files on the server" anti-pattern that leads to inconsistency, data loss, and security issues.
Simple Git-Based Deployment
cd /var/www/myapp
git pull origin main
composer install --no-dev --optimize-autoloader # PHP
npm ci --production && npm run build # Node.js
systemctl reload nginx
systemctl restart php-fpm
Automated Deployment Script
Create a standardized deploy script that handles backup, pull, dependencies, migrations, and service restart in the correct order. Add pre-deploy database backups so you can roll back if something goes wrong.
CI/CD with GitHub Actions
Automate deployments by connecting GitHub to your server. On every push to main, GitHub Actions can SSH into your server and run the deploy script. This eliminates manual deployment steps and ensures consistency.
For zero-downtime deployments, use a symlink-based release strategy: deploy to /releases/v123, then atomically update the /current symlink.
Part 14: Performance Optimization
Before optimizing, establish a baseline. Measure your current Time to First Byte (TTFB), request throughput, and resource usage under load.
Quick Performance Wins (80/20 Rule)
These four changes deliver 80% of possible performance gains with minimal effort:
- Enable gzip compression in Nginx - reduces transfer size by 60-80% for text-based content
- Set browser caching headers -
expires 30dfor static assets (CSS, JS, images) eliminates repeat downloads - Enable PHP OPcache - caches compiled bytecode, eliminating re-parsing on every request
- Add a CDN (Cloudflare free tier) - serves static content from edge locations worldwide
PHP-FPM Tuning
For a 4GB RAM server running PHP applications:
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500 # Prevent memory leaks
; OPcache
opcache.enable = 1
opcache.memory_consumption = 256
opcache.max_accelerated_files = 10000
opcache.revalidate_freq = 0 # Never check file timestamps (production)
Benchmarking
# Apache Bench: 1000 requests, 50 concurrent
ab -n 1000 -c 50 https://example.com/
# Measure TTFB
curl -o /dev/null -s -w "TTFB: %{time_starttransfer}s\n" https://example.com/
Part 15: Ongoing Maintenance
A production server is never "done." Regular maintenance prevents small issues from becoming outages.
Maintenance Schedule
- Daily: Check backup completion, monitor disk usage
- Weekly: Review auth logs, apply security updates
- Monthly: Run security audit (Lynis), test backup restore, review firewall rules, check SSL expiry
- Quarterly: Review user accounts, full server snapshot, practice disaster recovery
The Complete VPS Setup Checklist
- Update system, set hostname and timezone
- Create admin user, copy SSH keys
- Harden SSH (key-only, no root, custom port)
- Configure firewall (default deny, allow SSH + HTTP/S)
- Install fail2ban for brute-force protection
- Install Nginx, configure server blocks
- Set up SSL/TLS with Certbot
- Install and secure database
- Install application runtime
- Deploy application, configure reverse proxy
- Set up automated backups (3-2-1 rule)
- Configure monitoring and alerting
- Apply security hardening (sysctl, headers, scanning)
- Set up DNS and email authentication
- Test everything and document your setup
Download the Complete Reference
This article covers all the concepts in detail, but for a printable command reference you can keep at your desk, download our free VPS Setup Complete Guide 2026 - a comprehensive 15-page PDF with every command, configuration block, and checklist from this guide ready for daily use.
Recommended Reading
If you want to go deeper into any of the topics covered in this guide, these books from our collection provide comprehensive coverage:
- Linux Administration Fundamentals - Essential sysadmin skills for any server environment
- Linux System Hardening - Deep dive into securing Linux servers
- SSH Mastery: Secure Remote Administration - Advanced SSH techniques, tunneling, and key management
- NGINX Fundamentals - Master Nginx configuration for production
- Linux Web Server Setup - Complete guide to production web server deployment
- Linux Security Hardening - Comprehensive server security from kernel to application layer
- OpenSSH Configuration & Tunneling Guide - Advanced SSH features and secure access patterns
- Linux Security Auditing - Audit techniques and compliance frameworks
Related Articles
- 15 SSH Tips and Tricks Every Linux Admin Should Know
- SSL/TLS Certificate Setup: The Complete HTTPS Guide for Linux Servers
- Nginx Reverse Proxy: Route Multiple Apps Through One Server
- Nginx Configuration Mastery: From Installation to Production-Ready Server
- The Ultimate Linux Security Hardening Checklist for Production Servers
- 10 Bash Scripts That Will Automate Your Linux Server Management
- AlmaLinux 2026: The Complete Guide to Enterprise Linux
A VPS is a blank canvas. The difference between a server that gets compromised in its first week and one that runs reliably for years comes down to these first few hours of configuration. Follow this guide systematically, document every change you make, and maintain the discipline of regular updates, backups, and monitoring. Your future self - the one who gets paged at 3 AM when something goes wrong - will thank you for the preparation.