~/articles/ssh-security-best-practices

SSH Security Best Practices for Production Servers

Harden your SSH configuration with key-based authentication, port changes, fail2ban, jump hosts, and modern security techniques.

10 min read

SSH is the primary way Linux servers are administered. It is also the most common attack vector — automated bots scan the internet constantly, attempting brute-force logins against SSH on port 22. A default SSH configuration is a liability. This guide covers the essential hardening steps that every production server should have in place.

These practices range from quick wins (disabling password auth) to advanced techniques (certificate-based authentication and jump hosts). Implement them in order of priority for maximum security impact.

1. Disable Password Authentication

The single most impactful change you can make. Password authentication is vulnerable to brute-force attacks, credential stuffing, and weak passwords. Key-based authentication is immune to all of these.

# /etc/ssh/sshd_config

PasswordAuthentication no

ChallengeResponseAuthentication no

UsePAM yes

# Restart sshd to apply

$ sudo systemctl restart sshd

Before disabling password auth, ensure you have at least one SSH key configured and tested for your account. Lock yourself out by disabling passwords before adding a key, and you will need physical or console access to recover.

2. Use Strong SSH Keys

Not all SSH keys are created equal. Use Ed25519 keys — they are shorter, faster, and more secure than RSA. If you must use RSA (legacy compatibility), use at least 4096 bits.

# Generate an Ed25519 key (recommended)

$ ssh-keygen -t ed25519 -C "[email protected]"

# If Ed25519 is not supported, use RSA-4096

$ ssh-keygen -t rsa -b 4096 -C "[email protected]"

# Copy key to server

$ ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server

Always use a passphrase on your private key. Without one, anyone who gains access to your laptop or workstation has immediate access to all your servers. Use ssh-agentto avoid typing the passphrase repeatedly.

3. Disable Root Login

Never allow direct root login via SSH. Use a regular user account and escalate withsudo when needed. This provides an audit trail of who logged in and what they did.

# /etc/ssh/sshd_config

PermitRootLogin no

# Or allow root only with key (no password)

PermitRootLogin prohibit-password

4. Change the Default Port

Moving SSH off port 22 is not a security measure by itself — it is security through obscurity. But it dramatically reduces noise from automated scanners. Most bots only try port 22.

# /etc/ssh/sshd_config

Port 2222

# Update firewall before restarting sshd!

$ sudo ufw allow 2222/tcp

$ sudo systemctl restart sshd

# Connect on the new port

$ ssh -p 2222 user@server

Use your ~/.ssh/config file to remember custom ports per host, so you do not have to type -p 2222 every time.

5. Install fail2ban

fail2ban monitors log files for failed login attempts and automatically bans offending IP addresses using firewall rules. It is the standard defense against brute-force attacks.

$ sudo apt install fail2ban

$ sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

# /etc/fail2ban/jail.local

[sshd]

enabled = true

port = 2222    # match your SSH port

maxretry = 3

bantime = 3600 # 1 hour

findtime = 600  # within 10 minutes

Check banned IPs with sudo fail2ban-client status sshd. For cloud environments, consider using cloud-native security groups in addition to fail2ban for defense in depth.

6. Restrict Access with AllowUsers and AllowGroups

Limit which users can log in via SSH. This prevents service accounts, application users, and other non-administrative accounts from being used for SSH access.

# /etc/ssh/sshd_config — only these users can SSH in

AllowUsers admin deploy

# Or use groups (more scalable)

AllowGroups ssh-users

# Add users to the group

$ sudo usermod -aG ssh-users admin

7. Use Jump Hosts (Bastion Servers)

In production environments, internal servers should not be directly accessible from the internet. Use a hardened bastion (jump) host as the single entry point, and tunnel through it to reach internal hosts.

# Direct jump (OpenSSH 7.3+)

$ ssh -J bastion.example.com internal-server

# ~/.ssh/config — make it permanent

Host internal-*

  ProxyJump bastion.example.com

  User admin

# Now just: ssh internal-db

The bastion host should have the strictest hardening: no unnecessary software, key-only auth, fail2ban, and full audit logging. Restrict it to a single purpose — no other services should run on it.

8. Additional Hardening Options

# /etc/ssh/sshd_config — recommended hardening

MaxAuthTries 3                  # limit login attempts per connection

LoginGraceTime 30                # seconds to authenticate

ClientAliveInterval 300           # disconnect idle sessions

ClientAliveCountMax 2             # after 2 missed keepalives

X11Forwarding no                 # disable X11 unless needed

AllowTcpForwarding no             # disable port forwarding

AllowAgentForwarding no           # disable agent forwarding

PermitEmptyPasswords no

Also consider using SSH certificates instead of authorized_keys files for large teams. With certificates, a central CA signs user keys, and servers trust the CA — no need to distribute public keys to every server. Tools like Vault, step-ca, or Teleport make this manageable.

Quick Reference: sshd_config Hardening Checklist

Disable password authentication

Use Ed25519 keys with passphrases

Disable root login (or prohibit-password)

Change default port from 22

Install and configure fail2ban

Restrict users with AllowUsers/AllowGroups

Use jump hosts for internal servers

Set MaxAuthTries, LoginGraceTime, idle timeouts

Disable X11, TCP, and agent forwarding

Monitor auth logs and set up alerts

Related Tools