Tropic Host

VPS Security from Scratch: SSH Keys, Firewall, and Fail2ban

6 min read
Tropic

VPS Security from Scratch: SSH Keys, Firewall, and Fail2ban

A new VPS should not be considered secure immediately after Ubuntu is installed. Before access is configured, the server is already visible to scanners: they try SSH passwords, look for exposed control panels, and check for common vulnerabilities. In this guide, we will cover the basic steps for securing a VPS from scratch: creating a separate user, configuring SSH key authentication, restricting network ports with UFW, and adding Fail2ban. All steps work with Ubuntu 22.04 and 24.04 when the commands are run by a user with sudo privileges.

Before you start: make a list of ports

Do not open ports “just in case.” A typical web server needs:

  • 22/tcp — SSH; ideally restrict it by IP or move it to another port only as an additional measure;
  • 80/tcp — HTTP; usually needed to redirect to HTTPS and validate a certificate;
  • 443/tcp — HTTPS.

An application port such as 3000, 8080, or 9000 should not be accessible from the internet if Nginx or Caddy is running in front of it. Bind the application to 127.0.0.1 or allow its port only on the internal network. First, find out what is listening on the server:

sudo ss -tulpn

After installing Docker, check its iptables rules separately: publishing -p 3000:3000 can expose a port outside the expected setup. For a public service, it is safer to use an HTTPS proxy and keep the internal port hidden.

Step 1. Update the system and create an administrator

Connect to the VPS using the credentials you were given and install updates:

sudo apt update
sudo apt full-upgrade -y
sudo apt install -y ufw fail2ban unattended-upgrades

Create a personal user. Replace alex with your own name:

sudo adduser alex
sudo usermod -aG sudo alex

Do not remove the original access until you have tested the new account in a separate terminal window. This is an important rule: an SSH mistake is easier to fix while the current session is still open.

Step 2. Create an SSH key and install it on the server

On your Linux, macOS, or Windows PowerShell computer, run:

ssh-keygen -t ed25519 -C "alex@my-computer"

Press Enter to save the key in the default file, then set a passphrase. Never send the private key in a chat, put it in a repository, or copy it to the VPS. Only the file with the .pub extension should be transferred to the server:

ssh-copy-id alex@SERVER_IP

If ssh-copy-id is not available, display the public key with Get-Content $env:USERPROFILE\.ssh\id_ed25519.pub in PowerShell, then add it to the server:

sudo install -d -m 700 -o alex -g alex /home/alex/.ssh
sudo nano /home/alex/.ssh/authorized_keys
sudo chown alex:alex /home/alex/.ssh/authorized_keys
sudo chmod 600 /home/alex/.ssh/authorized_keys

Paste the key on a single line and test the login before disabling passwords:

ssh alex@SERVER_IP
sudo whoami

The expected result of the last command is root. If the SSH client cannot find the key, specify it explicitly: ssh -i ~/.ssh/id_ed25519 alex@SERVER_IP.

Step 3. Disable root login and password authentication

Back up the SSH configuration:

sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
sudo nano /etc/ssh/sshd_config

Make sure the file contains these parameters:

PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
KbdInteractiveAuthentication no
MaxAuthTries 3

On Ubuntu, settings may be located in /etc/ssh/sshd_config.d/*.conf, so check the effective configuration:

sudo sshd -t
sudo sshd -T | grep -E 'permitrootlogin|passwordauthentication|pubkeyauthentication|maxauthtries'

If sshd -t reports an error, do not restart the service until you fix the syntax. After the check succeeds, apply the settings:

sudo systemctl reload ssh

Keep the old SSH connection open and test key-based login in a new window. Disabling passwords without a verified key is the most common way to lose access to a server.

Step 4. Configure UFW without blocking SSH

UFW is a convenient wrapper around Linux network rules. First set secure defaults, then allow SSH and web ports:

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp comment 'SSH'
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'
sudo ufw enable

Confirm activation only after the SSH rule has been added. Review the result:

sudo ufw status verbose
sudo ufw status numbered

For a permanent office IP, you can restrict SSH to that address:

sudo ufw delete allow 22/tcp
sudo ufw allow from YOUR.PUBLIC.IP to any port 22 proto tcp comment 'SSH office'

Use this option only with a permanent IP and access to the provider console: if the address changes, you can lock yourself out.

How to open an application port temporarily

If you need to check a service directly, create a rule with a restricted source:

sudo ufw allow from YOUR.PUBLIC.IP to any port 8080 proto tcp

After testing, delete it using the number shown by ufw status numbered:

sudo ufw delete RULE_NUMBER

Do not use sudo ufw allow 1:65535/tcp: such a rule turns the firewall into a formality.

Step 5. Enable Fail2ban for SSH

Fail2ban analyzes logs and adds a temporary blocking rule after several failed login attempts. It does not replace SSH keys or a firewall, but it reduces the noise from automated brute-force attempts.

Create a local configuration so that package updates do not overwrite your settings:

sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local

Set reasonable values in the [DEFAULT] section:

[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
backend = systemd
banaction = ufw

[sshd]
enabled = true
port = 22

Start the service and check the jail:

sudo systemctl enable --now fail2ban
sudo fail2ban-client status
sudo fail2ban-client status sshd

In the sshd output, check the number of failed attempts and blocked addresses. Do not add your own IP to the ban list. For a permanent exception, use ignoreip = 127.0.0.1/8 YOUR.PUBLIC.IP in [DEFAULT].

Step 6. Enable automatic updates

Automatic updates are useful for security packages, but kernel reboots still need to be managed:

sudo dpkg-reconfigure -plow unattended-upgrades
sudo systemctl status unattended-upgrades

Once a week, check whether a reboot is required:

test -f /var/run/reboot-required && echo 'Требуется перезапуск'

Before manually rebooting, make sure you have a working key and access to the VPS console. After the reboot, check the application services, Nginx, UFW, and Fail2ban.

Step 7. Check the result from outside

Check the protection from another computer, not only by reading the configuration:

ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no alex@SERVER_IP

The command should end with a denial. Test a working key-based login separately:

ssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 alex@SERVER_IP

On the VPS, check listening addresses and rules:

sudo ss -lntup
sudo ufw status numbered
sudo fail2ban-client status sshd

The application port should listen on 127.0.0.1 if it is not intended for direct access. From an external host, you can use nmap SERVER_IP, but scan only your own server: checking someone else’s infrastructure without permission may violate the law and provider rules.

Backups and recovery

SSH protection does not protect against deleted data, an administrator’s mistake, or a compromised application. Store copies of the database, configuration, and user files separately from the VPS. A minimum setup is a daily backup, several versions from previous days, and periodic recovery tests.

Do not write passwords or tokens into plain-text scripts. If a secret is exposed, revoke it immediately and create a new one.

How to choose a VPS for a secure project

Security starts with control over the infrastructure. On a VPS, you can choose the operating system, firewall rules, access method, backups, and service placement yourself. For a small website or API, a stable network, SSD storage, and a recovery console are more important than an excess of virtual cores.

When launching a project on a VPS at tropic.host, define its public entry points in advance: in most cases, SSH, HTTP, and HTTPS are enough. Keep the database and control panels inside the internal network. As a project grows, separate VPS instances for the application, database, and monitoring help isolate failure domains.

Common mistakes

  • Disabling passwords before testing key-based login. Keep the current session open and test a new login separately.
  • Leaving 22/tcp open to the entire world when it could be allowed only from trusted addresses.
  • Publishing Docker ports directly. Check Docker and UFW rules first.
  • Installing Fail2ban and never checking the logs again. Review journalctl -u ssh and the jail status after changes.

Conclusion

Basic VPS protection does not take much time when the steps are performed in the right order: create a separate user, install an SSH key, verify access, disable root login and passwords, enable UFW, configure Fail2ban, and set up automatic updates. Then add backups, logging, and external monitoring.

These steps do not make a server invulnerable, but they eliminate the most common initial configuration mistakes. The next step is to adapt the security profile to the specific application: update dependencies, restrict service permissions, protect admin panels, and regularly check open ports.

FAQ

Do I need to change the default SSH port?

Not necessarily. Moving the port reduces automated noise, but it does not replace keys, disabled passwords, UFW, or Fail2ban. If you change the port, update the firewall rule and the port parameter in the Fail2ban jail.

What should I do if I lose access after configuring UFW?

Use the provider’s web or rescue console, then check the SSH rule and the sshd configuration. Do not close the old working session until the new login has been confirmed.

Is Fail2ban enough to protect SSH?

No. Fail2ban reacts to login failures that have already occurred. The foundation should be SSH keys, disabled password authentication, minimal user permissions, and a restricted firewall.

Can I allow SSH from only one IP address?

Yes, if the address is permanent: add a UFW allow from rule for that IP. Arrange provider-console access or another fallback in advance, because the connection will be blocked after the IP changes.

How often should I check VPS security?

After every change to SSH, the firewall, or Docker port publishing, run a port check and test login. As routine maintenance, review updates and logs at least once a week, and test backup recovery regularly.