First 10 steps after buying a VPS: security
Bots start guessing passwords on a new server within minutes of it getting a public IP. Below are ten steps that close the most common holes in about an hour: commands for Ubuntu and Debian, the differences for AlmaLinux and Rocky Linux, and the checks that keep you from locking yourself out of your own server.
The server you have just paid for is already visible to the whole internet. Bots scan addresses one after another and try to log in over SSH as root with popular passwords, so the first attempts by strangers show up in the log within the same hour. The good news is that simple things protect you from mass attacks: key-only login, closed ports you do not need, and updates. Everything below is done once, in about an hour, ideally before the site and any data land on the server.
The commands are for Ubuntu and Debian; the differences for AlmaLinux and Rocky Linux are noted separately. Run them as root or with sudo.
Before you start: a spare way in
Find the server console in your host's control panel: it is called VNC, KVM console or emergency console, and it shows the server's screen right in the browser. You can log in through it even when SSH no longer lets you in. While you change login settings, keep your current SSH session open and test every change in a second window. That way a config mistake costs a couple of minutes rather than a system reinstall.
1. Update the system
Hosts build their OS templates in advance, and by the time you install one it often has a dozen security fixes that have not been applied.
apt update && apt full-upgrade -y # Ubuntu, Debian
dnf upgrade -y # AlmaLinux, Rocky Linux
If the kernel was updated, reboot the server. On Ubuntu and Debian the sign is the file /var/run/reboot-required: if it exists, a reboot is needed.
2. Create a separate user
Working as root all the time is inconvenient and dangerous: any typo runs with full privileges, and every bot knows the name root. Create your own user and give it sudo rights:
adduser deploy
usermod -aG sudo deploy # Ubuntu, Debian
usermod -aG wheel deploy # AlmaLinux, Rocky Linux
On AlmaLinux and Rocky Linux the adduser command asks nothing, and the password is set separately: passwd deploy. Pick any name except the obvious admin and user.
3. Log in with an SSH key
Unlike a password, a key cannot be guessed by brute force. Create the key pair on your own computer, not on the server, and copy the public part to the server:
ssh-keygen -t ed25519 -C "laptop"
ssh-copy-id deploy@SERVER_IP
Windows 10 and 11 have a built-in OpenSSH client but no ssh-copy-id. In that case open the .pub file and paste its line into /home/deploy/.ssh/authorized_keys on the server; the .ssh folder must have permissions 700, the file 600, and the owner must be the user. Many hosts let you add a key when you order, in which case it is already in root's account.
Check in a new window that login works without a password and that sudo is available: ssh deploy@SERVER_IP, then sudo -v. Only then move on to the next step.
4. Turn off password and root login
It is easiest not to edit the main file but to put your settings in a separate file in /etc/ssh/sshd_config.d/. One important detail: for each option sshd takes the first value it finds, and it reads the files in this folder in alphabetical order. Ubuntu cloud images sometimes have a 50-cloud-init.conf file there with the line PasswordAuthentication yes, so give your file a number below 50, for example /etc/ssh/sshd_config.d/10-hardening.conf:
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
Check the syntax and which values the server will actually apply, then reload the settings:
sshd -t && sshd -T | grep -Ei 'permitrootlogin|passwordauthentication'
systemctl reload ssh # Ubuntu, Debian
systemctl reload sshd # AlmaLinux, Rocky Linux
Do not close the old session until you have logged in as your user with the key in a second window. An attempt to log in as root should be refused.
Moving SSH from port 22 to another one is optional. It removes almost all automated login attempts from the log, but the protection comes from the key, not the port number. If you do change it, open the new port in the firewall first. On Ubuntu 22.10 and later SSH is started through a systemd socket, and after changing Port you need to run systemctl daemon-reload and systemctl restart ssh.socket, otherwise the server keeps listening on 22.
5. Turn on the firewall
The rule is simple: all incoming connections are denied except those going to services that must be reachable from outside. For a website that means SSH, 80 and 443. On Ubuntu ufw is convenient; on Debian install it with apt install ufw:
ufw default deny incoming
ufw default allow outgoing
ufw allow OpenSSH
ufw allow 80,443/tcp
ufw enable
ufw status verbose
The order matters: allow SSH first, then enable. AlmaLinux and Rocky Linux already run firewalld, and SSH is allowed in it by default; the web ports are opened like this:
firewall-cmd --permanent --add-service=http --add-service=https
firewall-cmd --reload
firewall-cmd --list-all
If the server will run Docker, keep one of its quirks in mind: it writes its own iptables rules, and a published container port such as -p 3306:3306 becomes reachable from the internet even if ufw does not allow it. Publish internal ports on the local address only: -p 127.0.0.1:3306:3306. Plans for containers are collected in VPS for Docker.
Many hosts also have a network firewall in front of the server in their panel. It does not replace the system one, but it is useful as a second layer. DDoS protection is a separate service at the host's network level; plans that include it are in VPS with DDoS protection.
6. Install fail2ban
fail2ban reads the logs and temporarily bans addresses that keep failing to log in. With key-only login it mostly keeps the log clean, but it is also handy for other services: control panels, mail, the site's login page.
apt install fail2ban python3-systemd
Put your settings in /etc/fail2ban/jail.local, not in jail.conf, which gets overwritten on update:
[sshd]
enabled = true
backend = systemd
maxretry = 5
bantime = 1h
The backend = systemd line is needed on Debian 12 and later: there is no /var/log/auth.log file by default, the login log lives in journald, and without this line fail2ban finds nothing to read. Without your own settings it bans an address for 10 minutes after 5 failed attempts within 10 minutes. After editing, run systemctl restart fail2ban and check: fail2ban-client status sshd. On AlmaLinux and Rocky Linux the package comes from the EPEL repository: dnf install epel-release, then dnf install fail2ban and systemctl enable --now fail2ban.
7. Turn on automatic security updates
Most server break-ins use vulnerabilities for which a fix has already been released and simply was not installed. On Ubuntu the unattended-upgrades package is installed and enabled by default; check it with systemctl status unattended-upgrades. On Debian you install and enable it like this:
apt install unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades
By default it installs only security updates. The kernel will not be updated without a reboot: either reboot the server yourself when you see the sign from step 1, or enable an automatic night-time reboot with the Unattended-Upgrade::Automatic-Reboot and Automatic-Reboot-Time options in /etc/apt/apt.conf.d/50unattended-upgrades.
On AlmaLinux and Rocky Linux dnf-automatic does the same job. Install the package with dnf install dnf-automatic, set upgrade_type = security and apply_updates = yes in /etc/dnf/automatic.conf (without the latter, updates are only downloaded) and enable the timer: systemctl enable --now dnf-automatic.timer.
8. Check what the server is listening on
Host templates and control panels sometimes start services you never asked for. List all open ports:
ss -tulpn
Anything listening on 0.0.0.0 or [::] is reachable from outside if the firewall lets it through. Apart from SSH and the web server, there should be no such services. MySQL, MariaDB, PostgreSQL and Redis databases should listen only on 127.0.0.1: the Debian and Ubuntu packages are set up that way by default, but after an install through Docker or a third-party script it is worth checking. Stop services you do not need and remove them from startup: systemctl disable --now service_name. To see the server the way an outsider does, run this from another computer: nmap -Pn SERVER_IP.
9. Time, logs and monitoring
Run timedatectl to check that the clock is synchronized (the line System clock synchronized: yes). Wrong time breaks certificate checks, one-time codes and log analysis. Who logged in to the server and when is shown by last -a | head and journalctl -u ssh --since today (on AlmaLinux the service is called sshd).
Connect external uptime monitoring: there are plenty of free services that open your site once a minute and message you when something fails. Set up a separate alert for disk usage too: a full disk is a common reason why a site and its database stop working in the middle of the night.
10. Make a restore point and secure the host's panel
Once the server is set up, take a disk snapshot in the host's panel if the feature is available: you will be able to return to the clean, configured system in minutes. A snapshot does not replace backups, because it is stored at the same host. How to set up real copies off the server is covered in VPS backups: strategies and tools.
Turn on two-factor login for your account with the host. From that account the server can be reinstalled, the root password reset or the console opened, and a well-protected SSH means nothing if the account can be opened with a stolen password. Keep the private SSH key only on your own devices and protect it with a passphrase.
What not to do
- Do not disable SELinux on AlmaLinux and Rocky Linux at the first permission error. Almost always the right label on the site folder or a single permission via
setseboolis enough. - Do not set permissions to 777 on site folders. Files should be owned by the web server user or a separate site user, with 755 for folders and 644 for files.
- Do not run applications as root. A bot, a site or a task queue is fine with its own user without
sudo. - Do not expose the control panel and phpMyAdmin to everyone at their standard addresses. Allow access to them only from your own IP or through an SSH tunnel.
To pick a system for the new server, see VPS with Ubuntu, VPS with Debian and VPS with AlmaLinux. If the server is for a site moving from shared hosting, the next step is described in How to move a website from shared hosting to a VPS, and what to look at when choosing the plan itself is in How to choose a VPS: the 7 specs that matter.