Secure a new Linux VPS in 10 minutes
A server with a public IP sees its first automated login attempts within minutes of going online. These six steps close the usual ways in: missing patches, password logins, root over SSH and open ports. Pick your distribution once and every command below follows.
Before you start
- You can log in as root with an SSH key, not just the password. If not, do Connect to your VPS over SSH first.
- A fresh Ubuntu 22.04/24.04, Debian 11/12, AlmaLinux 8/9 or Rocky Linux 8/9 image from the client area.
- The client area open in a browser tab. Its Console button is your way back in if an SSH or firewall change goes wrong.
On Fully managed? You can skip this guide. Our engineers apply this baseline when they build your server and keep it patched afterwards. Running cPanel & WHM on the VPS? Skip Step 4: cPanel needs extra ports and manages its own firewall rules. Ask us if you're unsure.
Step 1: Update every package
Images are built ahead of time, so a new server is usually a few updates behind. Install them before anything else. The last line reboots only if a new kernel or core library needs it. Setting the time zone to IST makes logs and scheduled jobs line up with your working day.
apt update && apt full-upgrade -y
apt autoremove -y
timedatectl set-timezone Asia/Kolkata
[ -f /var/run/reboot-required ] && reboot
dnf upgrade -y
timedatectl set-timezone Asia/Kolkata
dnf needs-restarting -r || reboot
If the server reboots, wait a minute and log in again as root.
Step 2: Create a user with sudo rights
Working as root means one mistyped command can damage the whole system, and root is the account every bot tries first. Create your own user, give it administrator rights through sudo, and copy root's SSH key to it. Replace deploy with the name you want.
apt install -y sudo # already present on Ubuntu
adduser deploy # set a strong password when asked
usermod -aG sudo deploy
mkdir -p /home/deploy/.ssh
cp ~/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh && chmod 600 /home/deploy/.ssh/authorized_keys
useradd -m -s /bin/bash deploy
passwd deploy # set a strong password
usermod -aG wheel deploy
mkdir -p /home/deploy/.ssh
cp ~/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh && chmod 600 /home/deploy/.ssh/authorized_keys
restorecon -R /home/deploy/.ssh # fix SELinux labels on the copied key
Now open a second terminal on your computer and check that the new account works, including sudo. It should print root after asking for the password you just set:
ssh deploy@203.0.113.42
sudo whoami
Keep that password somewhere safe. With password logins turned off in the next step, you'll only need it for sudo and for the browser Console.
Step 3: Turn off root and password logins over SSH
With your own user working, close the two doors bots knock on: logging in as root, and logging in with a password. The settings go in a small file of their own, so package updates never overwrite them. It's named 00-… because SSH uses the first value it reads, which puts it ahead of distribution defaults such as Ubuntu's 50-cloud-init.conf.
cat > /etc/ssh/sshd_config.d/00-hardening.conf <<'EOF'
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
EOF
sshd -t && systemctl reload ssh
cat > /etc/ssh/sshd_config.d/00-hardening.conf <<'EOF'
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
EOF
sshd -t && systemctl reload sshd
On version 8, SSH doesn't read the sshd_config.d folder (grep -i '^Include' /etc/ssh/sshd_config returns nothing). Put the same lines at the top of /etc/ssh/sshd_config instead, with ChallengeResponseAuthentication no in place of the KbdInteractive… line.
sshd -t checks the syntax first, so a typo can't stop SSH from starting. Confirm what the server is actually using:
sshd -T | grep -Ei '^(permitrootlogin|passwordauthentication) '
# expected: permitrootlogin no / passwordauthentication no
Don't close your current session yet. From a new window, confirm ssh deploy@203.0.113.42 still works and ssh root@203.0.113.42 is refused. If the new login fails, delete the file with rm /etc/ssh/sshd_config.d/00-hardening.conf, reload SSH and check Step 2 again.
Step 4: Add a firewall and fail2ban
The firewall drops everything you haven't explicitly allowed. fail2ban watches the SSH log and bans an address for an hour after five failed attempts in ten minutes, which keeps logs readable and slows down scanners.
-
Turn on the firewall
Allow SSH first, then web traffic only if this server will host websites.
apt install -y ufw # Debian doesn't ship it ufw default deny incoming ufw default allow outgoing ufw allow 22/tcp ufw allow 80,443/tcp # web servers only ufw enable ufw status verbosednf install -y firewalld systemctl enable --now firewalld firewall-cmd --permanent --add-service=ssh # web servers only firewall-cmd --permanent --add-service=http --add-service=https firewall-cmd --reload firewall-cmd --list-allUsing Docker? Ports published with
-pskip ufw's rules entirely. Publish internal services on127.0.0.1only (for example-p 127.0.0.1:5432:5432) and put a reverse proxy in front of anything public. -
Install and configure fail2ban
apt install -y fail2ban python3-systemddnf install -y epel-release dnf install -y fail2ban python3-systemdThen, on either system, write your settings to
jail.local, which updates never touch. Put your office or home IP (our what's my IP tool shows it) inignoreipso you can't ban yourself:cat > /etc/fail2ban/jail.local <<'EOF' [DEFAULT] bantime = 1h findtime = 10m maxretry = 5 ignoreip = 127.0.0.1/8 ::1 198.51.100.7 [sshd] enabled = true backend = systemd EOF systemctl enable fail2ban && systemctl restart fail2ban fail2ban-client status sshdbackend = systemdreads the system journal, so it works on images that don't write/var/log/auth.log, including Debian 12.
Step 5: Install security updates automatically
Most break-ins use flaws that were patched weeks earlier. Let the server install security updates on its own every day; you still run full upgrades yourself when convenient.
apt install -y unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades # choose "Yes"
cat /etc/apt/apt.conf.d/20auto-upgrades # both lines should end in "1"
To let it reboot for kernel updates at a quiet hour, set these two lines in /etc/apt/apt.conf.d/50unattended-upgrades. The time is server time, which is IST after Step 1:
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:30";
dnf install -y dnf-automatic
sed -i -e 's/^upgrade_type.*/upgrade_type = security/' \
-e 's/^apply_updates.*/apply_updates = yes/' /etc/dnf/automatic.conf
systemctl enable --now dnf-automatic.timer
systemctl list-timers dnf-automatic.timer # shows the next run
dnf-automatic doesn't reboot by itself. Run dnf needs-restarting -r now and then, and reboot when it says so.
Step 6: Turn on backups, then run the checklist
Security also means being able to recover. Every VPS includes 3 on-demand snapshots. Take one now from the client area: it's a clean, hardened starting point you can roll back to in minutes. For protection against mistakes and bad updates, add the backups add-on (+20% of the plan price), which keeps daily copies for 14 days. See VPS add-ons.
Finally, confirm each step stuck. Run these as your new user:
| Check | Command | Expected |
|---|---|---|
| No pending security updates | sudo apt list --upgradable sudo dnf check-update | Empty, or nothing marked security |
| Root and passwords refused | sudo sshd -T | grep -E 'permitroot|passwordauth' | no · no |
| Firewall active | sudo ufw status sudo firewall-cmd --state | active running |
| fail2ban watching SSH | sudo fail2ban-client status sshd | A jail with a ban count |
| Automatic updates on | systemctl list-timers | grep -E 'apt|dnf' | A next run time |
| Baseline snapshot | Servicesyour VPSSnapshot | One snapshot dated today |
That's the baseline. From here, install only what you need, open firewall ports one at a time as you add services, and keep at least one recent snapshot before big upgrades.
VPS security troubleshooting
I locked myself out after changing SSH or the firewall
Click Console in the client area and log in as your new user with its password. Root's rules over SSH don't apply on the console. Undo the last change (sudo rm /etc/ssh/sshd_config.d/00-hardening.conf and reload SSH, or sudo ufw allow 22/tcp), then retry from Step 2. Forgot every password? Reset it in rescue mode.
sudo says the user "is not in the sudoers file"
The user isn't in the admin group, or you added it while that user was logged in. As root, run usermod -aG sudo deploy (Ubuntu, Debian) or usermod -aG wheel deploy (AlmaLinux, Rocky), then log out and back in so the new group applies.
fail2ban won't start
Test the configuration and read the error. The usual causes are a typo in jail.local or the missing python3-systemd package that backend = systemd needs.
sudo fail2ban-client -t
sudo journalctl -u fail2ban -n 30 --no-pager
My website stopped loading after I enabled the firewall
Ports 80 and 443 aren't open. Run sudo ufw allow 80,443/tcp, or on AlmaLinux and Rocky sudo firewall-cmd --permanent --add-service=http --add-service=https && sudo firewall-cmd --reload. Apps on other ports, such as a Node.js app on 3000, are better served through Nginx on 443 than by opening their port.
I banned my own IP with fail2ban
Connect from another network or use the Console, then lift the ban and add your IP to ignoreip in jail.local:
sudo fail2ban-client set sshd unbanip 198.51.100.7
Was this guide helpful?Your answer stays in this browser; nothing is sent unless you write to us below.
Related guides
All 16 guidesStill stuck? Ask an engineer.
Tickets and email are answered 24/7 in English and Hindi. Tell us the distribution, the step you were on and the exact error. We never need your passwords.