First 10 Things to Do on a New Linux VPS (Security Checklist)

A 30-minute copy-paste checklist for a fresh Debian, Ubuntu or AlmaLinux VPS, with a verification command after every step so you know it worked.

Published
Reading time
6 min

This is the short version: ten steps, the commands for each, and a check after every one so you know it took effect. It is meant to be run top to bottom in the first 30 minutes of a server's life, logged in as root. The reasoning behind each step is in how to harden a new Linux VPS in 10 steps; here we only do and verify. Commands are for Debian 12/13 and Ubuntu 22.04/24.04, with AlmaLinux and Rocky 8/9/10 where they differ. Replace deploy and 203.0.113.10 with your own user and address.

1. Update and reboot

bash
# Debian / Ubuntu
apt update && apt full-upgrade -y && reboot

# AlmaLinux / Rocky
dnf upgrade -y && reboot

Verify after logging back in:

bash
uname -r                    # the running kernel
apt list --upgradable       # Debian / Ubuntu: nothing listed
dnf check-update            # AlmaLinux / Rocky: no packages listed

2. Create a sudo user and give it your key

bash
# Debian / Ubuntu (minimal Debian images may need: apt install sudo -y)
adduser deploy
usermod -aG sudo deploy

# AlmaLinux / Rocky
adduser deploy
passwd deploy
usermod -aG wheel deploy

If you logged in as root with an SSH key, give the new user the same key:

bash
install -d -m 700 -o deploy -g deploy /home/deploy/.ssh
install -m 600 -o deploy -g deploy /root/.ssh/authorized_keys /home/deploy/.ssh/authorized_keys

If you logged in with a password, create a key on your own machine and copy it over instead:

bash
ssh-keygen -t ed25519 -C "you@example.com"
ssh-copy-id deploy@203.0.113.10

Verify from a second terminal on your own machine, leaving the root session open:

bash
ssh deploy@203.0.113.10
sudo whoami        # must print: root

Key types and agent setup are covered in SSH keys and disabling password login.

3. Turn off password and root login

Do not edit sshd_config itself. Use a drop-in file, which survives package upgrades:

bash
cat > /etc/ssh/sshd_config.d/00-hardening.conf <<'EOF'
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
EOF

sshd -t                      # no output means the syntax is valid
systemctl restart ssh        # Debian / Ubuntu
systemctl restart sshd       # AlmaLinux / Rocky

Tip: the file name matters. sshd uses the first value it reads for each setting, and drop-ins are read in alphabetical order. Cloud images often ship a 50-cloud-init.conf containing PasswordAuthentication yes; a file starting with 00- is read before it and wins, one named 99- would silently lose.

AlmaLinux and Rocky 8 do not read sshd_config.d. On those, put the same four lines at the top of /etc/ssh/sshd_config.

Verify what sshd will actually apply, then test from your own machine before closing the root session:

bash
sshd -T | grep -Ei '^(permitrootlogin|passwordauthentication|kbdinteractiveauthentication)'

# from your machine: both must be refused
ssh root@203.0.113.10
ssh -o PubkeyAuthentication=no -o PreferredAuthentications=password deploy@203.0.113.10

Both should end in Permission denied (publickey). A normal ssh deploy@203.0.113.10 must still work. Only then close the root session. If you do lock yourself out, the provider's console still gets you in.

4. Enable the firewall

bash
# Debian / Ubuntu
apt install ufw -y
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
bash
# AlmaLinux / Rocky
dnf install firewalld -y
systemctl enable --now firewalld
firewall-cmd --permanent --add-service=ssh
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload

Leave out 80 and 443 if the server will not serve web traffic.

Verify:

bash
ufw status verbose           # Debian / Ubuntu
firewall-cmd --list-all      # AlmaLinux / Rocky

Tip: Docker writes its own packet filter rules. A container port published as 5678:5678 is reachable from the internet even when ufw says it is denied. Publish to 127.0.0.1:5678:5678 when only a local reverse proxy needs it.

5. Install Fail2ban

bash
# Debian / Ubuntu
apt install fail2ban -y

# AlmaLinux / Rocky (the package is in EPEL)
dnf install epel-release -y
dnf install fail2ban -y

Write the jail explicitly instead of relying on distribution defaults:

bash
cat > /etc/fail2ban/jail.local <<'EOF'
[sshd]
enabled  = true
backend  = systemd
maxretry = 5
findtime = 10m
bantime  = 1h
EOF

systemctl enable --now fail2ban
systemctl restart fail2ban

backend = systemd matters on Debian 12 and later, which no longer write /var/log/auth.log by default; without it the jail has nothing to read and the service can fail to start.

Verify:

bash
fail2ban-client status sshd

More jails are in Fail2ban: block brute-force attacks.

6. Automatic security updates

bash
# Debian / Ubuntu
apt install unattended-upgrades -y
dpkg-reconfigure -plow unattended-upgrades

# AlmaLinux / Rocky
dnf install dnf-automatic -y

On AlmaLinux and Rocky set these two values in /etc/dnf/automatic.conf, then start the timer:

ini
upgrade_type = security
apply_updates = yes
bash
systemctl enable --now dnf-automatic.timer

Verify:

bash
unattended-upgrade --dry-run --debug | tail -n 5     # Debian / Ubuntu
systemctl list-timers 'apt-daily*' 'dnf-automatic*'  # next run time

Neither tool reboots by default, so a patched kernel waits for you. The longer treatment is in updating a Linux server safely.

7. Hostname and time sync

bash
hostnamectl set-hostname vps1.example.com
timedatectl set-timezone UTC
timedatectl

Verify: the timedatectl output must say System clock synchronized: yes. If it says NTP service: n/a, no time client is installed:

bash
apt install systemd-timesyncd -y                           # Debian / Ubuntu
dnf install chrony -y && systemctl enable --now chronyd    # AlmaLinux / Rocky
chronyc tracking                                           # where chrony is in use

Tip: after changing the hostname, make sure it resolves locally, or every sudo prints "unable to resolve host" and pauses. Add the name to the 127.0.1.1 line in /etc/hosts, or to the 127.0.0.1 line on AlmaLinux and Rocky.

8. Check what is listening

bash
ss -tulpn

Read the "Local Address" column. 127.0.0.1 and [::1] are local only. 0.0.0.0, * and [::] are open to every interface, and each of those lines should be something you can name and meant to expose. On a fresh server that normally means sshd on 22 and nothing else. Stop what you do not need:

bash
systemctl disable --now <service>

Verify from outside, on your own machine, because that is the view an attacker has:

bash
nmap -Pn 203.0.113.10

Outbound matters too. On our Cloud VPS, outbound mail ports 25, 465 and 587 are closed by default and opened on request, so a script that cannot send mail on day one is expected behaviour and not a firewall mistake of yours.

9. Add swap on small servers

With 4 GB of RAM or less, a small swap file turns an out-of-memory kill into a slow minute:

bash
swapon --show                # skip this step if something is listed
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
echo 'vm.swappiness=10' > /etc/sysctl.d/99-swappiness.conf
sysctl --system

Verify:

bash
swapon --show
free -h

How much to add, and how to remove it again, is in adding swap to a VPS.

10. Take a snapshot

The server is now in a known good state, before any application is on it. Take a snapshot or backup from your provider's control panel and note the date. If something goes wrong during the application install, you return to this point instead of repeating the checklist. Where the provider only takes automatic snapshots, wait for the next one before starting risky work.

Verify: the snapshot shows in the panel with today's date. It lives with the same provider as the server, so it is not a backup strategy, but it is the right tool for this moment.

Afterwards

Save the output of steps 7 and 8 somewhere. In three months, when you wonder whether a port was always open, you will have the answer.

#security#vps#linux#ssh#firewall#checklist

Keep reading

More from Security

All guides

Security

How to Harden a New Linux VPS in 10 Steps

The first hour on a fresh VPS: updates, a sudo user, SSH keys, firewall, Fail2ban, automatic patches and backups, with commands for Ubuntu and AlmaLinux.

3 min read →

Security

How to Install Fail2ban and Stop Brute-Force Attacks

Install Fail2ban, understand jails and bantime, protect SSH, Plesk, Nginx and WordPress logins, whitelist your own IP and read what it has banned.

3 min read →

Security

What Is a WAF and Does Your Website Need One?

How a web application firewall filters attacks before they reach your site, what it stops and what it cannot, the free options, and when it is worth turning on.

3 min read →