When you buy a VPS, the default installation offers no security layer at all. Root access is open, the firewall is off and there is no protection against brute-force attacks. In this guide we will work through, in detail, every security step needed to make your server production-ready on Ubuntu 22.04/24.04.

Related guides: What DNS is and how to change its settings · Domain names and WHOIS lookup · Guide to hosting types · Nginx configuration · Plesk panel management

SSH Security Hardening

SSH is the primary way into your server and the most attacked service. The default configuration allows root login with a password — that is the biggest security hole. As a first step we will move to key-based authentication.

Creating a New User

Instead of working directly as the root user, create a separate user with sudo privileges. This is critical both for security and for auditing.

bash
# Create a new user
adduser deploy

# Add to the sudo group
usermod -aG sudo deploy

# Switch to the user and test
su - deploy
sudo whoami  # should return root

Creating an SSH Key Pair

On your local machine (not on the server!) create an SSH key pair with the ED25519 algorithm. ED25519 offers higher security with a shorter key size than RSA.

bash
# Run this on your local machine
ssh-keygen -t ed25519 -C "deploy@myserver" -f ~/.ssh/myserver_key

# Copy the public key to the server
ssh-copy-id -i ~/.ssh/myserver_key.pub deploy@SERVER_IP

# Test it (it should connect without asking for a password)
ssh -i ~/.ssh/myserver_key deploy@SERVER_IP

Hardening the SSH Configuration

Once you have confirmed that key-based login works, update the SSH daemon configuration. The settings below disable root login, password authentication and X11 forwarding, and change the port.

bash
sudo nano /etc/ssh/sshd_config
ini
# /etc/ssh/sshd_config — lines to change
Port 2222
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
X11Forwarding no
AllowTcpForwarding no
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
UsePAM yes
AllowUsers deploy
bash
# Validate the configuration
sudo sshd -t

# Restart the service
sudo systemctl restart sshd

# Test from a new terminal
ssh -p 2222 -i ~/.ssh/myserver_key deploy@SERVER_IP

UFW Firewall Configuration

UFW (Uncomplicated Firewall) is a user-friendly interface to iptables. Blocking all incoming traffic by default and opening only the ports you need is the safest approach.

bash
# Install UFW and set the default policy
sudo apt install ufw -y
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Open the SSH port (use the new port if you changed it)
sudo ufw allow 2222/tcp comment "SSH"

# Web traffic
sudo ufw allow 80/tcp comment "HTTP"
sudo ufw allow 443/tcp comment "HTTPS"

# Enable UFW
sudo ufw enable

# Check the status
sudo ufw status verbose
PortProtocolDescriptionDefault
2222TCPSSH (customised port)Allow
80TCPHTTP web trafficAllow
443TCPHTTPS encrypted web trafficAllow
25TCPSMTP (sending mail)Open if needed
3306TCPMySQL/MariaDBKeep CLOSED
5432TCPPostgreSQLKeep CLOSED

Brute-Force Protection with Fail2ban

Fail2ban watches log files, detects repeated failed login attempts and blocks the attacker's IP address automatically. You can define jail rules for many services such as SSH, Nginx and Apache.

bash
# Installation
sudo apt install fail2ban -y

# Create a local configuration file (do not edit the original)
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local
ini
# /etc/fail2ban/jail.local
[DEFAULT]
bantime  = 3600
findtime = 600
maxretry = 3
banaction = ufw

[sshd]
enabled  = true
port     = 2222
logpath  = /var/log/auth.log
maxretry = 3
bantime  = 86400

[nginx-http-auth]
enabled  = true
logpath  = /var/log/nginx/error.log
maxretry = 5

[nginx-limit-req]
enabled  = true
logpath  = /var/log/nginx/error.log
maxretry = 10
findtime = 120
bash
# Start the service and check the status
sudo systemctl enable fail2ban
sudo systemctl start fail2ban

# List the active jails
sudo fail2ban-client status

# See the SSH jail details
sudo fail2ban-client status sshd

# Manually unban a specific IP
sudo fail2ban-client set sshd unbanip 192.168.1.100

Automatic Security Updates

Delaying security patches is an invitation to have known vulnerabilities (CVEs) exploited. With the unattended-upgrades package you can apply critical security updates automatically.

bash
# Installation
sudo apt install unattended-upgrades apt-listchanges -y

# Configure
sudo dpkg-reconfigure -plow unattended-upgrades

# Or configure manually
sudo nano /etc/apt/apt.conf.d/50unattended-upgrades
text
// /etc/apt/apt.conf.d/50unattended-upgrades
Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}-security";
    "${distro_id}ESMApps:${distro_codename}-apps-security";
};
Unattended-Upgrade::AutoFixInterruptedDpkg "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";
Unattended-Upgrade::Mail "admin@yoursite.com";

Log Monitoring and Alerting

Monitoring security events in real time is the key to detecting attacks early. With tools such as Logwatch or GoAccess you can produce daily reports and get instant email notifications on critical events.

bash
# Logwatch installation — sends a daily summary report
sudo apt install logwatch -y

# Configure
sudo nano /usr/share/logwatch/default.conf/logwatch.conf
ini
Output = mail
MailTo = admin@yoursite.com
MailFrom = logwatch@server
Detail = Med
Range = yesterday
Service = All
bash
# Watch the important log files live
sudo tail -f /var/log/auth.log     # SSH login attempts
sudo tail -f /var/log/ufw.log       # Firewall blocks
sudo tail -f /var/log/fail2ban.log  # Fail2ban actions

# Count failed SSH attempts in the last 24 hours
sudo grep "Failed password" /var/log/auth.log | grep "$(date +%b\ %d)" | wc -l

Two-Factor Authentication (2FA) for SSH

With the Google Authenticator PAM module you can add a second verification layer to SSH connections. A TOTP code will be requested in addition to key-based authentication.

bash
# Install the Google Authenticator PAM module
sudo apt install libpam-google-authenticator -y

# Run it as the user (not root!)
su - deploy
google-authenticator

# Answers to the questions:
# Time-based tokens? → y
# Update .google_authenticator file? → y
# Disallow multiple uses? → y
# Rate limiting? → y
bash
# Edit the PAM configuration
sudo nano /etc/pam.d/sshd

# Add to the end of the file:
auth required pam_google_authenticator.so

# Update the SSH configuration
sudo nano /etc/ssh/sshd_config

# Change these lines:
ChallengeResponseAuthentication yes
AuthenticationMethods publickey,keyboard-interactive

# Restart
sudo systemctl restart sshd

Additional Security Measures

On top of the basic hardening steps, the practices below take your server security to the next level.

  • Kernel hardening: optimise the sysctl parameters (disable ICMP redirects, SYN flood protection)
  • AppArmor/SELinux: limit applications' access to system resources with mandatory access control
  • ClamAV: run regular malware scans
  • rkhunter: detect hidden threats on the system with a rootkit scan
  • Disk encryption: encrypt the partitions holding sensitive data with LUKS
bash
# Kernel hardening parameters
sudo nano /etc/sysctl.d/99-security.conf
ini
# Disable ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0

# Disable IP source routing
net.ipv4.conf.all.accept_source_route = 0

# SYN flood protection
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 2048

# IP spoofing protection
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# Ignore ping requests (optional)
net.ipv4.icmp_echo_ignore_all = 1
bash
# Apply the parameters
sudo sysctl -p /etc/sysctl.d/99-security.conf

# Rootkit scan with rkhunter
sudo apt install rkhunter -y
sudo rkhunter --update
sudo rkhunter --check --sk

Security Checklist

StepStatusPriority
Disable root loginCritical
SSH key-based loginCritical
Change the SSH portHigh
UFW firewall activeCritical
Fail2ban installedHigh
Automatic security updatesHigh
Log monitoring and notificationsMedium
2FA enabledMedium
Kernel hardeningMedium
Rootkit scanLow

Modern Web Hosting and Server Infrastructure

A high-performance web hosting service rests on three basic infrastructure decisions: NVMe SSD disks (4-6 times the IOPS of a classic SATA SSD), the LiteSpeed Web Server or an Nginx + LSCache combination (9 times the request capacity of Apache) and CloudLinux + Imunify360 isolation. The hosting provider's control panel (cPanel, Plesk, DirectAdmin), daily backup policy, data centre location and support team response time also make a big difference. A local data centre gives visitors in your own country low latency, while European locations such as Hetzner Frankfurt or OVH Roubaix suit global traffic better. As a site grows, moving from shared hosting to a VPS and then to a dedicated server lets CPU/RAM/disk resources scale with what the website needs.