How to Harden Linux SSH: Keys, Fail2ban & Ciphers

Default SSH configurations ship with settings that expose systems to brute-force attacks, weak cipher negotiation, and credential stuffing. This guide walks through a complete Linux SSH hardening process covering key-based authentication, cipher suite restriction, Fail2ban integration, and sshd_config best practices.


1. Switch to Key-Based Authentication

Password authentication is the primary attack vector for SSH brute-force campaigns. Replacing it with public key authentication eliminates that surface entirely.

Generate an Ed25519 Key Pair

Ed25519 is preferred over RSA-2048 for its shorter key size, faster performance, and resistance to timing attacks. Generate the pair on the client:

ssh-keygen -t ed25519 -C "user@hostname" -f ~/.ssh/id_ed25519

If you must use RSA for legacy compatibility, use a minimum of 4096 bits:

ssh-keygen -t rsa -b 4096 -C "user@hostname" -f ~/.ssh/id_rsa

Deploy the Public Key to the Server

ssh-copy-id -i ~/.ssh/id_ed25519.pub user@remote-host

Or manually append the public key on the server:

cat ~/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keys
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

Correct permissions on .ssh and authorized_keys are mandatory — SSH will silently reject keys if permissions are too permissive.


2. Harden sshd_config

The bulk of SSH hardening lives in /etc/ssh/sshd_config. The settings below address authentication policy, access control, and session behaviour.

Core Authentication Settings

# /etc/ssh/sshd_config

# Disable password and keyboard-interactive authentication
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no   # OpenSSH 8.7+

# Disable root login entirely
PermitRootLogin no

# Enforce key-based auth only
AuthenticationMethods publickey

# Reduce authentication attempt window
LoginGraceTime 30
MaxAuthTries 3

# Disable unused authentication methods
UsePAM yes                        # Keep PAM for account/session management
PermitEmptyPasswords no

Access Restriction

Limit SSH access to specific users or groups rather than relying solely on firewall rules:

# Whitelist specific users
AllowUsers deploy ansible sysadmin

# Or restrict by group
AllowGroups ssh-users

# Restrict to specific source addresses (if infrastructure allows)
# This is best handled at the firewall, but can be layered here
Match Address 10.0.0.0/8,192.168.0.0/16
    PermitRootLogin no

Session and Connection Settings

# Disconnect idle sessions
ClientAliveInterval 300
ClientAliveCountMax 2

# Disable forwarding unless explicitly required
AllowAgentForwarding no
AllowTcpForwarding no
X11Forwarding no

# Prevent accidental tunnel exposure
GatewayPorts no

# Limit concurrent unauthenticated connections (start:rate:full)
MaxStartups 10:30:60

# Restrict to IPv4/IPv6 as needed
AddressFamily inet

Change the Default Port (Optional but Useful)

Changing the port from 22 does not add meaningful security, but it eliminates a significant volume of automated noise from scanners, reducing log pollution and Fail2ban workload:

Port 2222

Update your firewall accordingly and ensure ListenAddress is set if you need to bind to a specific interface.

Apply the Configuration

# Validate config before restarting
sshd -t

# Restart the daemon
systemctl restart sshd

Always keep an active session open when testing changes. Lock yourself out and you may need console access to recover.


3. Restrict Cipher Suites and Key Exchange Algorithms

Default OpenSSH builds permit legacy algorithms that have known weaknesses. Restricting to modern, vetted options removes this exposure.

Identify Current Negotiated Algorithms

ssh -vv user@remote-host 2>&1 | grep -E "kex|cipher|mac"

Or audit the server’s advertised algorithms:

nmap --script ssh2-enum-algos -p 22 remote-host

Enforce Strong Algorithms in sshd_config

Add the following to /etc/ssh/sshd_config:

# Key Exchange: prefer Diffie-Hellman with curve25519 and ECDH
KexAlgorithms curve25519-sha256,,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512

# Ciphers: AES-GCM provides authenticated encryption; ChaCha20 is strong for high-latency links
Ciphers ,,

# MACs: prefer ETM (Encrypt-then-MAC) variants
MACs ,

# Host key algorithms presented by the server
HostKeyAlgorithms ssh-ed25519,,rsa-sha2-512,rsa-sha2-256

This configuration drops CBC-mode ciphers, MD5/SHA1-based MACs, and diffie-hellman-group1/group14 (Logjam-vulnerable) key exchange.

Regenerate Host Keys

If the server was provisioned with weak RSA keys, regenerate and remove them:

# Remove legacy key types
rm /etc/ssh/ssh_host_dsa_key* /etc/ssh/ssh_host_ecdsa_key*

# Regenerate with strong types only
ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ""
ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N ""

Update sshd_config to reference only the new host keys:

HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key

4. Configure Fail2ban for SSH

Even with key-based auth enabled, Fail2ban remains useful: it blocks reconnaissance activity, handles misconfigured clients that retry with passwords, and adds defence-in-depth against future misconfigurations.

Install Fail2ban

# Debian/Ubuntu
apt install fail2ban -y

# RHEL/Rocky/AlmaLinux
dnf install fail2ban -y

Configure the SSH Jail

Create a local override file rather than editing the default config, which may be overwritten on upgrade:

# /etc/fail2ban/jail.d/sshd.conf

[sshd]
enabled  = true
port     = ssh
filter   = sshd
backend  = systemd
logpath  = /var/log/auth.log   # Debian/Ubuntu
#logpath = /var/log/secure     # RHEL-based
maxretry = 3
findtime = 300
bantime  = 3600
ignoreip = 127.0.0.1/8 ::1 10.0.0.0/8 192.168.0.0/16

If you changed the SSH port:

port = 2222

Adjust Bantime for Repeat Offenders

Fail2ban supports incremental banning via bantime.multiplier:

# /etc/fail2ban/jail.local

[DEFAULT]
bantime.increment = true
bantime.multiplier = 2
bantime.maxtime = 604800   # 7 days cap

This doubles the ban duration for each subsequent offence from the same IP.

Enable and Test

systemctl enable --now fail2ban

# Check jail status
fail2ban-client status sshd

# Manually ban/unban an IP for testing
fail2ban-client set sshd banip 198.51.100.10
fail2ban-client set sshd unbanip 198.51.100.10

# View banned IPs
fail2ban-client get sshd banned

5. Additional sshd_config Best Practices

A few remaining settings worth including in any production hardening baseline:

# Suppress version information in the SSH banner
DebianBanner no

# Disable SFTP if not required, or restrict it
# Subsystem sftp /usr/lib/openssh/sftp-server

# Use a legal/warning banner (optional, required in some compliance frameworks)
Banner /etc/ssh/banner.txt

# Log level for auditing (INFO is default; VERBOSE logs key fingerprints)
LogLevel VERBOSE

# Restrict SSH to protocol version 2 (redundant in OpenSSH 7.0+ but explicit)
# Protocol 2  # Deprecated directive; OpenSSH drops SSHv1 by default

Conclusion

A hardened SSH configuration is not a single change — it is a layered policy combining key-based authentication, cipher restriction, access control, and active threat response. Disabling password auth and enforcing Ed25519 keys removes the most common attack vector; stripping legacy ciphers and key exchange algorithms eliminates cryptographic downgrade paths; Fail2ban provides rate-limiting and automated block response; and a tightly scoped sshd_config reduces the exploitable feature surface. Validate each change with sshd -t before applying, and audit the configuration periodically using tools like ssh-audit (ssh-audit remote-host) to catch regression or newly deprecated algorithms.

Rick Donato

Want to learn more about Operating Systems and Databases ?

Here is our hand-picked selection of the best courses you can find online:
Linux Mastery course
Linux Administration Bootcamp
Learn Linux in 5 Days
Complete VMware Administration course
Complete SQL Bootcamp
and our recommended certification practice exams:
Delta Practice Tests