Offer Free .in, .co.in or .com domain for year one with web or WordPress hosting on a 12-month+ term, plus free migration. See the offer
VPS & dedicated

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.

  • Updated
  • 9 min read
  • Level Intermediate

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.
Hands-on timeAbout 10 minutes
DowntimeOne reboot, only if a new kernel installs

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

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

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

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.

  1. 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 verbose

    Using Docker? Ports published with -p skip ufw's rules entirely. Publish internal services on 127.0.0.1 only (for example -p 127.0.0.1:5432:5432) and put a reverse proxy in front of anything public.

  2. Install and configure fail2ban

    apt install -y fail2ban python3-systemd

    Then, 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) in ignoreip so 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 sshd

    backend = systemd reads 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";

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:

CheckCommandExpected
No pending security updatessudo apt list --upgradable
sudo dnf check-update
Empty, or nothing marked security
Root and passwords refusedsudo sshd -T | grep -E 'permitroot|passwordauth'no · no
Firewall activesudo ufw status
sudo firewall-cmd --state
active
running
fail2ban watching SSHsudo fail2ban-client status sshdA jail with a ban count
Automatic updates onsystemctl list-timers | grep -E 'apt|dnf'A next run time
Baseline snapshotServicesyour VPSSnapshotOne 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 guides

Still 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.