InfraFenceInfraFence

Live Security Operations

Get Started

Secure a new VPS: the checklist

Last updated:

A new VPS is on the internet the moment it boots, and scanners reach it within minutes. These ten steps are the first hour of work on a fresh Debian, Ubuntu or RHEL-family server (RHEL, Rocky Linux, AlmaLinux, Oracle Linux, CentOS Stream, Fedora), in the order that keeps you from locking yourself out.

Run them as a user with sudo, keep one SSH session open while you change SSH or the firewall, and know where your provider's web console is: it works when SSH does not.

1. Install every update

Cloud images are often weeks old. Update everything before you install anything else, and reboot if the kernel or core libraries changed.

Debian / Ubuntu
sudo apt update && sudo apt full-upgrade -y
[ -f /var/run/reboot-required ] && echo "reboot needed"
RHEL, Rocky Linux, AlmaLinux, Oracle Linux, Fedora
sudo dnf upgrade --refresh -y
sudo dnf needs-restarting -r

On the RHEL family, needs-restarting -r says whether a reboot is needed. Reboot with sudo reboot and log in again.

2. Create your own user with sudo

Work as a named user with sudo, not as root: actions are logged under a name, and root login can be turned off in the next step.

Debian / Ubuntu
sudo adduser deploy
sudo usermod -aG sudo deploy
RHEL, Rocky Linux, AlmaLinux, Oracle Linux, Fedora
sudo useradd -m deploy
sudo passwd deploy
sudo usermod -aG wheel deploy

Copy your SSH key to the user (ssh-copy-id deploy@SERVER_IP from your computer), log in as the user from a second terminal and check sudo -v. Then make sure only root has user ID 0 and no account has an empty password: the first command must print only root, the second nothing.

Any distribution
awk -F: '$3 == 0 {print $1}' /etc/passwd
sudo awk -F: '$2 == "" {print $1}' /etc/shadow

3. Lock down SSH

Keys only, no root login, few attempts per connection. The full walk-through, with the checks and the port change, is in How to stop SSH brute force attacks. The short version, once your key login works:

Any distribution with sshd_config.d
sudo tee /etc/ssh/sshd_config.d/00-hardening.conf > /dev/null <<'EOF'
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
MaxAuthTries 4
EOF
sudo sshd -t && echo "config OK"
Debian / Ubuntu
sudo systemctl reload ssh
RHEL, Rocky Linux, AlmaLinux, Oracle Linux, Fedora
sudo systemctl reload sshd

Warning: Test a new key login from a second terminal before you close the current session. If grep -i '^Include' /etc/ssh/sshd_config prints nothing (RHEL 8 and its rebuilds), put these lines in /etc/ssh/sshd_config instead.

4. Turn on the firewall

Deny everything inbound, then open only what the server offers. Here SSH on 22 and a web server on 80 and 443; leave out what you do not run.

Debian / Ubuntu (ufw)
sudo apt install -y ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80,443/tcp
sudo ufw enable
sudo ufw status verbose
RHEL family (firewalld)
sudo dnf install -y 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

Warning: Allow your SSH port before ufw enable or the reload, or new SSH connections are refused. If you moved SSH to another port, open that one.

  • On RHEL 9 and its rebuilds, the default zone also allows cockpit: remove it with sudo firewall-cmd --permanent --remove-service=cockpit if you do not use it, then reload.
  • Docker publishes container ports with its own iptables rules, which ufw does not see: a port published with -p 8080:80 is open even if ufw says otherwise. Publish on 127.0.0.1:8080:80 when only the local proxy needs it.
  • Most providers also have a cloud firewall or security group. Use it as a second layer with the same rules.

5. Turn on automatic security updates

Most compromised servers run software with a fix that was available for weeks. Let the server install security updates by itself.

Debian / Ubuntu
sudo apt install -y unattended-upgrades
sudo tee /etc/apt/apt.conf.d/20auto-upgrades > /dev/null <<'EOF'
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
EOF
sudo unattended-upgrade --dry-run --debug 2>&1 | tail -n 20

Which updates are installed is set in /etc/apt/apt.conf.d/50unattended-upgrades (security updates on Ubuntu; the security and point-release updates on Debian). Automatic reboots are off unless you set Unattended-Upgrade::Automatic-Reboot "true"; there.

RHEL, Rocky Linux, AlmaLinux, Oracle Linux
sudo dnf install -y dnf-automatic
sudo sed -i -e 's/^upgrade_type.*/upgrade_type = security/' \
  -e 's/^apply_updates.*/apply_updates = yes/' /etc/dnf/automatic.conf
sudo systemctl enable --now dnf-automatic.timer
systemctl list-timers dnf-automatic.timer
  • CentOS Stream publishes no security advisories, so upgrade_type = security installs nothing there: use upgrade_type = default.
  • Fedora 41 and later use dnf5: install dnf5-plugin-automatic and set the same options in /etc/dnf/automatic.conf (see man dnf5-automatic).
  • Neither tool reboots by default. Plan a reboot when the kernel is updated.

6. Set up backups that leave the server

A backup on the same server, or only in the same provider account, goes when the server or the account does. Keep at least one copy elsewhere, encrypted, and test a restore. Provider snapshots are useful on top, not instead.

restic is one simple option: encrypted, deduplicated, to SFTP, S3 and others. It is packaged for Debian and Ubuntu, and in EPEL for the RHEL family. Replace BACKUP_HOST with your backup server; restic asks for a repository password, which you must store outside this server.

Debian / Ubuntu
sudo apt install -y restic
Rocky Linux, AlmaLinux (EPEL)
sudo dnf install -y epel-release
sudo dnf install -y restic
Any distribution
sudo restic -r sftp:backup@BACKUP_HOST:/srv/restic/$(hostname) init
sudo restic -r sftp:backup@BACKUP_HOST:/srv/restic/$(hostname) backup /etc /home /root /var/www
sudo restic -r sftp:backup@BACKUP_HOST:/srv/restic/$(hostname) snapshots

Dump databases to a file before the backup runs (mysqldump --single-transaction, pg_dump) rather than copying their data directories while they run. Then prove that a restore works:

Any distribution
sudo restic -r sftp:backup@BACKUP_HOST:/srv/restic/$(hostname) restore latest --target /tmp/restore-test --include /etc/hostname
cat /tmp/restore-test/etc/hostname

Note: Schedule the backup with a systemd timer or cron, and check its result: a backup job that has been failing silently for months is the common case.

7. Set up monitoring and alerts

You need to know, without logging in, when the server is down, when the disk fills up and when someone logs in or attacks it. At the minimum:

  • An external uptime check on the site or service, from outside your provider.
  • An alert on disk space and on failed services.
  • A record of who logged in, and an alert on attacks and on changes to system files.

Until something does that for you, these show recent logins, errors since boot and free disk space:

Any distribution
last -n 20
sudo journalctl -p err -b --no-pager | tail -n 30
df -h /

8. Close the ports you do not need

List what is listening. An address of 0.0.0.0 or [::] means every interface, so the internet, unless the firewall stops it.

Any distribution
sudo ss -tulpn
  • Stop and disable the services you do not use.
  • Databases and caches used only by the same server listen on localhost: bind-address = 127.0.0.1 for MySQL and MariaDB, listen_addresses = 'localhost' for PostgreSQL, bind 127.0.0.1 -::1 for Redis.
  • Never expose the Docker API (2375, 2376) or Redis without a password to the internet: both are common ways in for crypto miners.
Stop a service you do not need
sudo systemctl disable --now SERVICE_NAME

Then check from outside, from another machine with nmap installed. Only the ports you opened in step 4 should answer.

From another machine
nmap -Pn SERVER_IP

9. Keep the clock in sync

Logs from different servers only line up when their clocks agree, and TLS, two-factor codes and incident timelines all depend on the time. Check it:

Any distribution
timedatectl

System clock synchronized: yes is what you want. If it says no:

Debian / Ubuntu
sudo apt install -y systemd-timesyncd
sudo timedatectl set-ntp true
RHEL, Rocky Linux, AlmaLinux, Oracle Linux, Fedora
sudo dnf install -y chrony
sudo systemctl enable --now chronyd
chronyc tracking

Note: Some images ship chrony instead of systemd-timesyncd. Use whichever is there: installing one removes or replaces the other.

10. Keep the logs long enough

When something happens, the first question is what happened before. Make the system journal persistent, with a size cap and a retention you choose:

Any distribution
sudo mkdir -p /etc/systemd/journald.conf.d
sudo tee /etc/systemd/journald.conf.d/retention.conf > /dev/null <<'EOF'
[Journal]
Storage=persistent
SystemMaxUse=1G
MaxRetentionSec=1year
EOF
sudo systemctl restart systemd-journald
journalctl --disk-usage
  • Size the cap to the disk: 1 GB is plenty for a small server.
  • How long you must keep logs depends on your contracts and obligations (for example NIS2 incident reporting). Decide it, write it down and set it here.
  • Logs kept only on the server can be deleted by whoever breaks in. Send a copy to another machine or a log service too.
  • Files in /var/log (web server, auth.log on Debian and Ubuntu) are rotated by logrotate: the rotate count in /etc/logrotate.d/ sets how many old files are kept.

What InfraFence checks and watches on a new server

After the install, InfraFence checks the server every day, or when you ask, and keeps watching it in real time. It covers several steps of this list, not all of them:

  • 40–50 hardening checks, depending on the server: SSH settings, accounts with user ID 0 or an empty password, databases open to the internet, pending security updates, an operating system past its end of life, permissions of sensitive files, web server settings, exposed secrets.
  • Known vulnerabilities (CVE) of the installed packages, from the Debian, Ubuntu, AlmaLinux and Rocky Linux security trackers, ranked by real-world exploitation.
  • A security score from 0 to 100, showing what lowers it. The safe fixes are applied with one click and can be undone; the ones that could lock you out, like SSH login and the port, are explained step by step instead.
  • Real-time detection and blocking: SSH and service brute force, web attacks, bots, malware, outbound and DNS threats, and changes to critical files.
  • Alerts by email, Slack, Discord, Telegram or webhook, and CPU, memory, disk and network metrics.

It does not take backups, sync the clock or keep your logs for you: those steps stay yours.

Questions

Is the provider's firewall enough, or do I need ufw or firewalld too?
Use both when you can. The provider's firewall stops traffic before it reaches the server; the one on the server still protects it if the cloud rules are changed by mistake or the server moves.
Are automatic updates safe on a production server?
Security updates on the stable releases of these distributions are small and tested, and are much less risky than running known holes. Leave automatic reboots off and plan them, and keep a tested backup.
How long should I keep logs?
There is no single number. It depends on your contracts and on rules such as NIS2 for the companies it covers. Decide a period, keep a copy away from the server, and make sure the server keeps at least enough to investigate the last weeks.

Try it free on 1 server

Starter is free for one server, with full protection and automatic blocking. No credit card.

Install the agent as root, with your token in place of <TOKEN>:

curl -fsSL https://github.com/infrafence/infrafence-agent/releases/latest/download/install.sh | sudo bash -s -- --token <TOKEN>

The token is created in the dashboard, in Servers › Add server.