SSH Brute Force Starts In Under 60 Seconds
A fresh Ubuntu 24.04 VPS exposed to the public internet sees its first SSH connection attempt within 30 to 60 seconds of going live. By the end of the first hour it has been scanned by automated bots for several thousand passwords. None of this is exotic. It is the baseline noise floor of the modern internet.
The 15-minute hardening run in this post does not turn a VPS into a fortress. It closes the obvious doors so the baseline noise floor does not become a successful login. Anything more sophisticated is a separate exercise. Everything below is meant to run on a freshly-provisioned root-access VPS, in order, no skipping.
Pre-Flight At Provisioning
Two decisions made at provisioning time save work later:
- Provide an SSH key during VPS creation. Most providers (Hetzner, AWS, Vultr, DigitalOcean) accept an SSH public key in the create-server form. The new VPS boots with that key already in
/root/.ssh/authorized_keys. You log in with the key on the first try. No password ever set on root. - Pick a region in line with your data-residency requirements. A reversible choice at provisioning, much harder to migrate later.
Now SSH in as root and run through the steps below.
Step 1: Patch Everything First
Before changing any other configuration, apply pending security updates.
apt-get update
apt-get -y upgrade
apt-get -y install unattended-upgrades apt-listchanges
dpkg-reconfigure --priority=low unattended-upgrades
The unattended-upgrades package is configured to automatically apply security updates on a daily cron. The next CVE that drops gets patched without you logging in.
Reboot if the upgrade installed a new kernel:
[ -f /var/run/reboot-required ] && reboot
Step 2: Create A Non-Root User With Sudo
Operating as root is a habit worth breaking, on a fresh VPS more than anywhere else.
adduser --disabled-password ops
usermod -aG sudo ops
mkdir -p /home/ops/.ssh
cp /root/.ssh/authorized_keys /home/ops/.ssh/
chown -R ops:ops /home/ops/.ssh
chmod 700 /home/ops/.ssh
chmod 600 /home/ops/.ssh/authorized_keys
Test it in a second terminal before continuing:
ssh ops@<vps-ip>
sudo whoami # should print "root"
Keep the original root SSH session open until the verification works. If something breaks, you still have a way in.
Step 3: Lock Down SSH
Drop a hardening file at /etc/ssh/sshd_config.d/99-hardening.conf:
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
UsePAM yes
AuthenticationMethods publickey
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
LoginGraceTime 30
AllowUsers ops
Restart SSH:
systemctl restart ssh
Verify the new policy is in effect:
sshd -T | grep -E "permitrootlogin|passwordauthentication|allowusers"
If all three show the hardened values, root login over SSH is closed and password auth is gone. Drop the original root SSH session.
Changing the SSH port is a separate decision. It is security-by-obscurity at best, useful only for cutting brute-force log noise. If you want the noise reduction, change it. If you want hardening, the steps below matter much more.
Step 4: Firewall With UFW
Open only what you need.
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
# Add other ports your service needs:
# ufw allow 80/tcp
# ufw allow 443/tcp
ufw --force enable
ufw status verbose
If your application server lives behind a load balancer or reverse proxy on another machine, restrict the application ports to the proxy's IP only:
ufw allow from 10.0.0.5 to any port 8080
Step 5: Fail2ban For The Brute-Force Floor
Even with key-only SSH, bots hammer port 22 forever. Fail2ban temporarily bans IPs that hit the failed-attempt threshold.
apt-get -y install fail2ban
cat > /etc/fail2ban/jail.d/sshd.local <<'EOF'
[sshd]
enabled = true
port = 22
maxretry = 3
bantime = 1h
findtime = 10m
EOF
systemctl enable --now fail2ban
fail2ban-client status sshd
The bans are temporary, in-memory, and refresh on the schedule above. The log noise drops within an hour.
Step 6: Kernel Network Hardening
A small set of sysctls closes common network-level attacks. Drop a file into /etc/sysctl.d/:
cat > /etc/sysctl.d/99-hardening.conf <<'EOF'
# Source IP spoofing protection
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# Ignore ICMP broadcast (smurf attack)
net.ipv4.icmp_echo_ignore_broadcasts = 1
# Ignore bogus ICMP responses
net.ipv4.icmp_ignore_bogus_error_responses = 1
# SYN flood mitigation
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 2048
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 5
# Disable source routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
# Disable ICMP redirect acceptance
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
# Log martian packets
net.ipv4.conf.all.log_martians = 1
EOF
sysctl --system
Step 7: Time Sync
A drifting clock breaks TLS, breaks log correlation, and silently breaks anything that uses timestamps for authentication (JWTs, signed requests).
apt-get -y install chrony
systemctl enable --now chrony
chronyc tracking
The Last offset value in the tracking output should be near zero after a minute of sync.
Step 8: Disable Services You Will Not Use
Check what is listening and trim it.
ss -tulpn
systemctl list-units --type=service --state=running
The defaults on Ubuntu 24.04 are reasonable, but check for avahi-daemon, cups, bluetoothd, and snapd (if not using snaps). Disable what you do not need:
systemctl disable --now snapd.socket snapd.service
apt-get -y purge snapd
Step 9: Basic Audit Logging
Enable auditd to log security-relevant events:
apt-get -y install auditd audispd-plugins
systemctl enable --now auditd
The defaults log identity changes, sudo activity, and file system modifications to /var/log/audit/audit.log. For a production server, point this stream at your central log collector when one is configured.
Step 10: A Periodic Reality Check
Set a weekly cron that emails the operator a summary of what is listening, who has logged in, and what failed:
cat > /etc/cron.weekly/hardening-snapshot <<'EOF'
#!/bin/sh
{
echo "=== Listening sockets ==="
ss -tulpn
echo
echo "=== Recent successful logins ==="
last -20
echo
echo "=== Recent SSH failures ==="
journalctl -u ssh -n 50 --no-pager | grep -i fail
echo
echo "=== fail2ban status ==="
fail2ban-client status sshd
} | mail -s "Weekly hardening snapshot $(hostname)" ops@example.com
EOF
chmod +x /etc/cron.weekly/hardening-snapshot
Requires mailutils and an SMTP relay. If the VPS has no outbound mail path, swap the mail call for a writeout to a known directory and grab the file on schedule.
What This Does Not Cover
The 15-minute run is the baseline. What it does not cover:
- Application-layer hardening (web server, database, Redis, etc.).
- Network segmentation across multiple servers.
- Centralized log aggregation and SIEM integration.
- Intrusion detection beyond fail2ban (OSSEC, Wazuh, Falco).
- Backup automation and tested restore procedures.
- Compliance frameworks (SOC 2, ISO 27001, PCI-DSS).
Each is a separate body of work and the right configuration depends on the application. The hardening above is what every Ubuntu 24.04 VPS should have before you put anything on it.
Verification In Five Commands
When the run is done, these five commands confirm the state:
sshd -T | grep -E "permitrootlogin|passwordauthentication"
ufw status verbose
fail2ban-client status sshd
chronyc tracking
ss -tulpn
SSH root login no, password auth no, ufw active with a clean rule set, fail2ban running, chrony in sync, and only the services you expect listening: the box is ready for production workload.
Bottom Line
Fifteen minutes after first boot, the VPS has key-only SSH, a working firewall, fail2ban on port 22, kernel-level network hardening, automatic security updates, time sync, and a weekly self-report. The brute-force baseline noise floor stops mattering. You spent five minutes more than the cloud-init shell would have, and you got a meaningfully harder target.
If you want this run automated across a fleet, or layered on top of application-specific hardening (Magento, WordPress, Node.js, Kubernetes, Postgres), that is what our servers management and security and compliance work covers. Repeatable, version-controlled, and audited.
Need help with this?
Our team handles this kind of work daily. Let us take care of your infrastructure.
Related Articles
The Ultimate Guide to Linux Server Management in 2025
A comprehensive guide to modern Linux server management covering automation, containerization, cloud integration, AI-driven operations, security best practices, and essential tooling for 2025.
Server & DevOpsFixing "421 Misdirected Request" for Plesk Sites on Ubuntu 22.04 After Apache Update
Resolve the 421 Misdirected Request error affecting all HTTPS sites on Plesk for Ubuntu 22.04 after an Apache update, caused by changed SNI requirements in the nginx-to-Apache proxy chain.
Server & DevOpsHow to Set Up GlusterFS on Ubuntu
A complete guide to setting up a distributed, replicated GlusterFS filesystem across multiple Ubuntu 22.04 nodes, including installation, volume creation, client mounting, maintenance, and troubleshooting.