Browse docs

Harden your VPS

Lock down SSH, add a firewall, ban brute-force attempts and turn on automatic security updates.

Updated

On this page

A new VPS is reachable from the whole internet within seconds of booting, and automated scanners will start trying to log in almost immediately. This guide walks through the changes that stop the vast majority of those attacks. Plan for about 20 minutes.

The commands target Debian and Ubuntu. Where Rocky Linux, AlmaLinux or other RHEL-family systems differ, there's a note.

Before you start: keep the VPS's Console tab open in the dashboard. It connects to the VM's screen directly, not over SSH, so it keeps working even if you lock yourself out of SSH. See Locked out? at the end. If the server already holds data you care about, take a backup from the Backup tab first.

1. Update everything

Start from a fully patched system:

apt update && apt full-upgrade -y

RHEL family: dnf upgrade -y

If a new kernel was installed (on Debian/Ubuntu, /var/run/reboot-required exists), reboot with reboot and reconnect.

2. Create a regular user with sudo

Logging in as root means every typo runs with full privileges. Create your own user (replace alice with your name everywhere below):

adduser alice
usermod -aG sudo alice

RHEL family: useradd -m alice && passwd alice && usermod -aG wheel alice

Minimal Debian images may not ship sudo; install it with apt install sudo.

3. Log in with an SSH key

Key-based login can't be brute-forced the way a password can.

On your own computer (not the VPS), create a key if you don't have one yet:

ssh-keygen -t ed25519

Then copy the public key to the new user:

ssh-copy-id alice@YOUR_SERVER_IP

If you added an SSH key when you created the VPS, it's already in root's authorized_keys. You can copy it to the new user from the server instead:

mkdir -p /home/alice/.ssh
cp /root/.ssh/authorized_keys /home/alice/.ssh/
chown -R alice:alice /home/alice/.ssh
chmod 700 /home/alice/.ssh && chmod 600 /home/alice/.ssh/authorized_keys

Test it now in a new terminal, without closing your current session:

ssh alice@YOUR_SERVER_IP
sudo whoami   # should print: root

Don't continue until this works.

4. Turn off root and password logins

Create /etc/ssh/sshd_config.d/00-hardening.conf:

sudo nano /etc/ssh/sshd_config.d/00-hardening.conf
# Only key-based logins, and never directly as root
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey

# Fewer guesses per connection, shorter time to log in
MaxAuthTries 3
LoginGraceTime 30

X11Forwarding no

# Optional: only these users may log in over SSH
# AllowUsers alice

Why the file name starts with 00-

sshd uses the first value it reads for each setting, and it reads the files in /etc/ssh/sshd_config.d/ in alphabetical order. On BitQate VPSes, cloud-init sets up the root password at first boot and writes 50-cloud-init.conf containing PasswordAuthentication yes. A file named, say, hardening.conf would sort after it and be silently ignored. 00- sorts first and wins, even if cloud-init rewrites its own file later, for example after you change the root password from the dashboard.

Check that the main config includes the directory (Debian 12+, Ubuntu 22.04+ and RHEL 9 do by default):

grep -n '^Include' /etc/ssh/sshd_config
# Include /etc/ssh/sshd_config.d/*.conf

Validate the configuration, then confirm the values sshd will actually use:

sudo sshd -t
sudo sshd -T | grep -Ei '^(permitrootlogin|passwordauthentication|kbdinteractiveauthentication)'
# permitrootlogin no
# passwordauthentication no
# kbdinteractiveauthentication no

Apply it:

sudo systemctl restart ssh     # Debian/Ubuntu
sudo systemctl restart sshd    # RHEL family

Test again from a new terminal before closing your existing session:

ssh alice@YOUR_SERVER_IP                            # works
ssh root@YOUR_SERVER_IP                             # Permission denied
ssh -o PubkeyAuthentication=no alice@YOUR_SERVER_IP # Permission denied (publickey)

Changing the SSH port (optional)

Moving SSH off port 22 cuts down log noise from bots but is not a real security control. If you do it, add Port 2222 (or similar) to the same file and open the new port in your firewall first (next step).

On Ubuntu 22.10 and later, SSH is started through systemd socket activation, so a port change also needs:

sudo systemctl daemon-reload
sudo systemctl restart ssh.socket

5. Add a firewall

Allow only what you actually serve. Every VPS has a public IPv6 block in addition to any IPv4 address, so make sure the rules cover both.

Debian/Ubuntu (ufw):

sudo apt install ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp          # or your custom SSH port
# sudo ufw allow 80/tcp        # only if you run a web server
# sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose

Check that /etc/default/ufw contains IPV6=yes (the default), so the same rules apply to IPv6.

RHEL family (firewalld):

sudo dnf install firewalld
sudo systemctl enable --now firewalld
sudo firewall-cmd --permanent --add-service=ssh
# sudo firewall-cmd --permanent --add-service=http --add-service=https
sudo firewall-cmd --reload
sudo firewall-cmd --list-all

Running Docker on the VPS? Ports you publish with docker run -p or Docker Compose bypass ufw: Docker inserts its own rules ahead of it, so ufw deny won't block them. Publish only on localhost (-p 127.0.0.1:8080:80) and put a reverse proxy in front, or filter in the DOCKER-USER chain. See Docker's packet filtering and firewalls page.

6. Ban repeated failed logins with fail2ban

With password logins off, brute force can't succeed, but fail2ban still blocks scanners early and keeps your logs readable.

sudo apt install fail2ban python3-systemd

RHEL family: enable EPEL first (sudo dnf install epel-release), then sudo dnf install fail2ban.

Create /etc/fail2ban/jail.local (never edit jail.conf, which is replaced on upgrades):

[DEFAULT]
bantime  = 1h
findtime = 10m
maxretry = 5
backend  = systemd
# Never ban your own address:
# ignoreip = 127.0.0.1/8 ::1 YOUR_HOME_IP

[sshd]
enabled = true
# port = 2222   # if you changed the SSH port

backend = systemd reads the systemd journal, which works on every current distribution. Debian 12 and later no longer write /var/log/auth.log by default, so file-based setups from older guides silently match nothing there.

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

7. Install security updates automatically

Debian/Ubuntu:

sudo apt install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades   # answer "Yes"
sudo unattended-upgrade --dry-run --debug                   # check it works

By default only security updates are installed. Kernel updates still need a reboot: check for /var/run/reboot-required occasionally, or set Unattended-Upgrade::Automatic-Reboot "true"; together with a reboot time in /etc/apt/apt.conf.d/50unattended-upgrades if a short nightly restart is acceptable.

RHEL family:

sudo dnf install dnf-automatic
sudo systemctl enable --now dnf-automatic-install.timer

8. Shut off what you don't use

List everything that is listening for connections:

sudo ss -tulpn

For each entry, ask whether it needs to be reachable from the internet:

  • Services you don't need: sudo systemctl disable --now SERVICE.
  • Services only the VPS itself uses, such as a database behind your app: bind them to 127.0.0.1 in their own config instead of 0.0.0.0.

9. Keep backups

Hardening lowers the odds of a compromise; backups decide how bad one is. Take regular backups from the Backup tab, and before risky changes. Keep copies of anything irreplaceable somewhere off the server too.

Locked out?

The Console tab on your VPS page opens the VM's screen in your browser. It doesn't use SSH, so none of the settings above affect it.

  1. Open Console → Connect to console. The VM must be running.

  2. Log in as root with the password shown on the Overview tab. Forgot it? Set a new one under Settings, then restart the VM when asked.

  3. Undo the SSH changes and restart SSH:

    mv /etc/ssh/sshd_config.d/00-hardening.conf /root/
    systemctl restart ssh    # or sshd on RHEL family
    
  4. If the firewall is the problem: ufw disable (or systemctl stop firewalld), fix the rules, and enable it again.

Still stuck? Open a support ticket and tell us what you changed last.

Checklist

  • System fully updated
  • Personal user with sudo; logging in with an SSH key works
  • 00-hardening.conf in place; sshd -T shows root and password logins off
  • Firewall enabled, covering IPv4 and IPv6, with only needed ports open
  • Docker-published ports (if any) bound to localhost or filtered
  • fail2ban running with the sshd jail
  • Automatic security updates enabled
  • Unused services disabled, internal services bound to localhost
  • A recent backup

Further reading

Still stuck?

Our team can look at your specific setup.