Lock Down SSH in 10 Minutes: A Hardening Checklist for Linux

Lock on keyboard

If you put a server on the public internet with port 22 open and password login enabled, you are going to get knocked on. Not maybe — guaranteed. The bots find you within minutes, and they never sleep. The good news: locking SSH down properly takes about ten minutes and roughly six lines of config.


🔐 Lock Down SSH in 10 Minutes: A Hardening Checklist

This is the same config I put on every Linux box I run — from a $5 VPS to the Proxmox cluster in my office. By the end of this post you’ll have key-only authentication, no root login, a non-default port, a whitelisted user group, fail2ban, and a workflow that won’t lock you out of your own server.

📋 Prerequisites

  • A Linux server you can already SSH into (Debian/Ubuntu in the examples — same idea on RHEL/Alma/Rocky).
  • A non-root user with sudo rights. If you’ve been logging in as root, create one first (adduser christopher && usermod -aG sudo christopher).
  • A second terminal ready before you touch sshd. This is the rule that has saved me more times than I can count.

🗝️ Step 1: Get on Keys, Get Off Passwords

Passwords are the single biggest reason servers get popped. Even a 16-character password is brute-force-able if a bot has a year and you’re not rate-limiting. SSH keys aren’t — a 256-bit ed25519 key is, for practical purposes, uncrackable.

Generate one on your laptop (not the server), then copy the public half up:

# Generate a modern ed25519 key (faster, smaller, stronger than RSA)
ssh-keygen -t ed25519 -C "christopher@laptop"

# Copy the public key to your server (still using password — for now)
ssh-copy-id -p 22 [email protected]

# Test that key-based login works BEFORE you disable passwords
ssh [email protected]

If ssh-copy-id isn’t available, append the contents of ~/.ssh/id_ed25519.pub to ~/.ssh/authorized_keys on the server manually. Don’t skip the test login — verify keys work before you disable passwords below.


🛡️ Step 2: The Six-Line Hardening File

On modern OpenSSH you don’t edit /etc/ssh/sshd_config directly — you drop a file in /etc/ssh/sshd_config.d/ and it gets included automagically. That way distro upgrades don’t trample your changes.

# /etc/ssh/sshd_config.d/99-hardening.conf
# Drop this file in the .d/ directory — it overrides the main sshd_config.

# 1. No passwords. Keys only.
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no

# 2. No root over SSH. Log in as a normal user and sudo.
PermitRootLogin no

# 3. Move off port 22 — kills 99% of drive-by bots.
Port 2222

# 4. Whitelist who can SSH in (group is easier to manage than user list).
AllowGroups ssh-users

# 5. Idle session timeout (server kicks dead sessions after ~15 min).
ClientAliveInterval 300
ClientAliveCountMax 2

# 6. Limit retries before disconnect.
MaxAuthTries 3
LoginGraceTime 30

What each setting does:

  • PasswordAuthentication no — bots can’t try admin/admin anymore. They’ll still knock, but they’ll fail instantly.
  • PermitRootLogin no — even if someone steals your root password, they can’t use it for SSH. They’d need a key in /root/.ssh/authorized_keys, which shouldn’t exist.
  • Port 2222 — this isn’t real security (it’s “security through obscurity”), but it cuts log noise by ~99% because the drive-by scanners only check 22. Pick anything between 1024 and 65535 that isn’t already used.
  • AllowGroups ssh-users — only users in this group can SSH in at all. Now you can adduser someone for sudo without giving them remote access.
  • ClientAliveInterval / CountMax — closes dead sessions so you don’t have ghost connections eating slots.
  • MaxAuthTries 3 — three failed auths and the connection drops. Pairs well with fail2ban below.

🧪 Step 3: Apply It Without Locking Yourself Out

This is where people brick their own server. Do these in order:

# Validate the config BEFORE you restart — a typo here can lock you out.
sudo sshd -t

# Add yourself to the whitelist group.
sudo groupadd ssh-users 2>/dev/null
sudo usermod -aG ssh-users $USER

# Open the new port in the firewall BEFORE reloading sshd.
sudo ufw allow 2222/tcp
sudo ufw delete allow 22/tcp   # only after you've tested 2222

# Reload sshd — this keeps your current session alive.
sudo systemctl reload ssh

Now — do not close your existing SSH session. Open a brand-new terminal and try to connect on the new port:

# In a SECOND terminal — keep your existing SSH session open.
# If you can log in on the new port, you're safe to close the old one.
ssh -p 2222 [email protected]

If that works, great — close the old session. If it doesn’t, you still have the old session to fix whatever broke. The number-one cause is forgetting to open the new port in the firewall before reloading sshd.


🚧 Step 4: fail2ban for Everything Else

Keys + non-default port + AllowGroups stops the vast majority of attacks at the door. fail2ban handles the rest: anybody who hits MaxAuthTries three times gets their IP firewalled for an hour. Free, painless, no false positives if your config is right.

# Install fail2ban for everything bots still try (script kiddies, password sprays).
sudo apt install -y fail2ban

# Drop a jail config for sshd. Use the new port you set above.
sudo tee /etc/fail2ban/jail.d/sshd.local >/dev/null <<'EOF'
[sshd]
enabled  = true
port     = 2222
maxretry = 3
findtime = 10m
bantime  = 1h
EOF

sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd

Run sudo fail2ban-client status sshd any time to see who’s currently banned. On a fresh server with port 22 closed, this list stays empty for weeks. On port 22 it’d be ten IPs deep within an hour.


💻 Bonus: A Sane Client Config

Now that your server is on a non-default port, you don’t want to type ssh -p 2222 christopher@host a dozen times a day. Drop an entry in ~/.ssh/config on your laptop:

# ~/.ssh/config on your laptop — so you don't have to type -p 2222 every time.

Host pegasus
    HostName pegasus.example.com
    Port 2222
    User christopher
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes
    ServerAliveInterval 60

Now ssh pegasus uses the right user, key, and port automatically. Add one block per server you care about.


✅ The Checklist

  1. Generate an ed25519 key on your laptop and copy it to the server.
  2. Verify key-based login works before changing anything else.
  3. Drop 99-hardening.conf into /etc/ssh/sshd_config.d/.
  4. sshd -t to validate, open the new port in the firewall, then systemctl reload ssh.
  5. Test the new port from a second terminal before closing your existing session.
  6. Install fail2ban with a jail pointed at the new port.
  7. Add a matching block to ~/.ssh/config on every client you use.

You’re done! Your server is now harder to break into than 99% of what’s on the public internet, and the noise in /var/log/auth.log just dropped to almost nothing.

Next post in this series I’ll cover the things you do after the basics: SSH certificates (so you can revoke keys across a fleet without touching every server), 2FA via TOTP, and how to wire all of this into Ansible so a fresh VPS is hardened in one playbook run. If you’ve got a specific SSH question — or a story about the time you locked yourself out — I’d love to hear it.

Thanks!

Christopher Engelhardt

Rainier IT

Rainier IT

Leave a Reply

Your email address will not be published. Required fields are marked *