VPS Configuration and Hardening Guide
Base reference: [Ubuntu Server Guide](https://ubuntu.com/server/docs), [CIS Benchmarks](https://www.cisecurity.org/cis-benchmarks), [SSH Hardening - Mozilla](https://infosec.mozilla.org/guidelines/openssh)
Nota
Note: Throughout this document there are example users and placeholder values which you'll need to replace with your own username or custom options.
Example server data
This is the data your VPS provider gives you when creating the server instance.
| Field | Value |
|---|---|
| IP | `203.0.113.42` |
| User | `root` |
| Password | `Xk9#mP2@vL5nQw` |
`203.0.113.x` is a range reserved for documentation (RFC 5737), it's not a real IP.
First connection:
ssh [email protected]Step 1 - Update the system
apt update && apt upgrade -yNota
Why? A newly created server has outdated packages from the base image. Updating closes known vulnerabilities before doing anything else. It's the first step in any serious hardening guide (CIS, NIST).
Step 2 - Create an unprivileged user with sudo
adduser santikz
usermod -aG sudo santikzTo skip the password prompt when using sudo:
visudoAdd at the end of the file:
santikz ALL=(ALL) NOPASSWD:ALL(create a user with any name of your preference, e.g.: admin, dev, sysadmin, marcelo, etc…)
Nota
Why? Always operating as `root` is bad practice. If someone compromises your session, they have total control of the system. An unprivileged user limits the radius of action of an attacker. `NOPASSWD` in sudo is a balance between security and acceptable convenience in dev environments where key-based authentication is already used.
Step 3 - Generate SSH keys and configure public key access
How SSH keys work
SSH uses asymmetric cryptography: two mathematically linked keys are generated. The private key stays on your local machine and never leaves it. The public key is copied to the server. When you try to connect, the server sends a challenge encrypted with your public key; only whoever has the corresponding private key can resolve it. If it matches, you get in, without needing to type a password. This eliminates the risk of someone intercepting or guessing your password, since it never travels over the network. `ed25519` is the algorithm currently recommended by Mozilla and NIST: shorter, faster, and more secure than traditional RSA.
Generate the keys (on your local machine, not the server)
# Linux / macOS
ssh-keygen -t ed25519 -f ~/.ssh/myserver
# Windows (PowerShell)
ssh-keygen -t ed25519 -f C:\Users\santikz\.ssh\myserverThis generates two files:
Atención
Note: give the file a descriptive name so you can differentiate between keys from different servers.
Copy the public key to the server with SCP
First we copy the `.pub` file to the server using `scp` (ssh copy). At this point we still use password because we haven't configured key-based access yet:
# Linux / macOS
scp ~/.ssh/myserver.pub [email protected]:/home/santikz/
# Windows (PowerShell)
scp C:\Users\tuusuario\.ssh\myserver.pub [email protected]:/home/santikz/Add the public key to authorized_keys (on the server)
Connect to the server and run:
ssh [email protected]mkdir -p ~/.ssh
cat ~/myserver.pub >> ~/.ssh/authorized_keys
rm ~/myserver.pubThe `authorized_keys` file is the list of public keys the server accepts for authentication. The `>>` operator appends the key to the end of the file without deleting existing ones, which allows having multiple keys (e.g., from different team members).
Verify permissions on the server
SSH is strict about the permissions of these files. If they're misconfigured, it rejects the key even if everything else is correct.
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keysConfigure the local SSH file
This file allows defining aliases for connecting without typing username, IP and key every time.
Linux / macOS - file: `~/.ssh/config`
Host myserver
User santikz
HostName 203.0.113.42
IdentityFile ~/.ssh/myserverWindows - file: `C:\Users\tuusuario\.ssh\config`
Host myserver
User santikz
HostName 203.0.113.42
IdentityFile C:\Users\tuusuario\.ssh\myserverWith this you'll be able to connect simply with:
ssh myserverIf public key login doesn't work
Verify it's enabled on the server:
sudo nano /etc/ssh/sshd_configMake sure these lines exist and are not commented out:
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keysStep 4 - Configure SSH: disable password and root login
sudo nano /etc/ssh/sshd_configFind and modify (or add) these lines:
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM noRestart SSH:
sudo systemctl restart sshd**Before closing your current session**, open a second terminal and verify you can connect with the key. If something fails and you close the session, you'll be locked out of the server.
Nota
Why? Disabling `root` login via SSH is one of the first steps in any hardening checklist. Disabling passwords eliminates the most common attack vector: brute force and credential stuffing. From here on, only the correct private key can get in.
Importante
Note: this disables SSH login using a password, which means if we lose the private key we'll lose access to the server, so make sure to secure the key with a backup. If you feel unsure, leave root login and password login enabled, but set a long and secure password for root.
Step 5 - Install Fail2Ban
sudo apt install fail2ban -y
sudo systemctl enable fail2ban
sudo systemctl start fail2banBasic configuration (create override file):
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.localVerify that the `[sshd]` section is enabled:
[sshd]
enabled = true
maxretry = 5
bantime = 3600
findtime = 600Restart:
sudo systemctl restart fail2banSee banned IPs:
sudo fail2ban-client status sshdNota
Why? Even though we've disabled password login, the SSH port is still exposed to automated scanners that try thousands of connections. Fail2Ban monitors system logs and automatically bans IPs that exceed the number of failed attempts. Reduces noise in logs and protects against enumeration attacks.
Step 6 - Configure the Firewall with UFW
sudo apt install ufw -y**Important:** Allow SSH **before** activating the firewall or it will lock you out.
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPSActivate:
sudo ufw enableSee status:
sudo ufw status verboseNota
Why? A newly created server has all ports exposed to the internet. UFW (Uncomplicated Firewall) is the standard frontend for `iptables` on Ubuntu. The rule is simple: only what is explicitly allowed is open. This drastically reduces the attack surface.
Optional Steps
Docker
Follow the official guide, it's the most up-to-date and varies by Ubuntu version:
https://docs.docker.com/engine/install/ubuntu/
After installing, add your user to the docker group so you don't have to use sudo each time:
sudo usermod -aG docker santikz
newgrp dockerMonitoring tools and utilities
sudo apt install -y htop btop tmux curl git wget unzip| Tool | What it's for |
|---|---|
| `htop` | Interactive process monitor |
| `btop` | Modern resource monitor (CPU, RAM, network) |
| `tmux` | Terminal multiplexer, keeps SSH sessions active |
| `curl` | Make HTTP requests from the terminal |
| `git` | Version control |
| `wget` | Download files from the terminal |
Share

Written by
Santiago Bugnón
CTO @ Nebula Solutions

