S
Home Network Security ยท Updated 2026-07-20

SSH Hardening

SSH is the primary remote administration path into most Linux, network, and appliance infrastructure โ€” which makes a misconfigured SSH daemon one of the highest-value targets on the network. A compromised SSH service typically means full shell access. This guide covers hardening OpenSSH (client and server) aligned to the CIS Benchmarks (Distribution Independent Linux, section 5.2 "Configure SSH Server"), NIST SP 800-53 Rev. 5 control mappings, and NIST SP 800-123 (Guide to General Server Security).

Scope: OpenSSH server (sshd) and client configuration on Linux/Unix hosts. Network device SSH (Cisco, Juniper, etc.) follows the same principles โ€” key-based or MFA auth, strong algorithms, restricted access โ€” implemented via vendor-specific syntax.


1. Authentication โ€” Eliminate Password Auth

Password authentication over SSH is the single highest-risk default. It is subject to credential stuffing, brute force, and reuse from breached credential dumps. CIS and NIST both treat this as a baseline control, not optional hardening.

Enforce Public Key Authentication

# /etc/ssh/sshd_config
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitEmptyPasswords no
AuthenticationMethods publickey
  • Use Ed25519 keys for new deployments (ssh-keygen -t ed25519) โ€” smaller, faster, and resistant to the implementation weaknesses that have affected some RSA/ECDSA usage. Where FIPS 140-3 validated modules are required, use RSA 3072-bit or ECDSA P-256/P-384.
  • Never generate or distribute keys with no passphrase for interactive human users. Passphrase-protect private keys and rely on ssh-agent for session convenience.
  • Rotate keys on personnel offboarding, suspected compromise, or a fixed schedule (e.g., annually for standing access).
# Generate a modern key
ssh-keygen -t ed25519 -a 100 -C "user@host-purpose"

# FIPS-constrained environments
ssh-keygen -t rsa -b 3072 -C "user@host-purpose"

Disable Root Login

PermitRootLogin no

Administrators should authenticate as a named user and elevate via sudo, preserving individual accountability in audit logs. If emergency direct root access is unavoidable (e.g., console/OOB only), scope it there โ€” never over the network.

Multi-Factor Authentication

Align to NIST SP 800-63B IAL2/AAL2 for privileged remote access by layering MFA on top of public key auth:

# /etc/ssh/sshd_config
AuthenticationMethods publickey,keyboard-interactive
# /etc/pam.d/sshd โ€” example with Google Authenticator PAM module
auth required pam_google_authenticator.so

For enterprise environments, prefer centrally managed MFA (Duo, FIDO2/WebAuthn hardware keys via OpenSSH's native sk-ecdsa-sha2-nistp256@openssh.com key types, or a SAML/OIDC-backed SSH CA) over per-host TOTP secrets, which are harder to revoke at scale.


2. Access Control โ€” Restrict Who Can Connect

Allow-List Users and Groups

Deny-by-default; explicitly allow only the accounts and groups that require SSH access.

AllowUsers admin_jsmith admin_mchen svc_backup
AllowGroups ssh-users sysadmins
DenyUsers root
DenyGroups guest

Restrict by Network Source

Layer OS-level and network-level controls rather than relying on sshd_config alone:

# iptables โ€” allow SSH only from the management network / bastion
iptables -A INPUT -p tcp --dport 22 -s 10.10.5.0/24 -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j DROP
# /etc/hosts.allow and /etc/hosts.deny (TCP wrappers, where supported)
sshd : 10.10.5.0/255.255.255.0
sshd : ALL

Use a Bastion / Jump Host

Do not expose SSH on production hosts directly to user networks or the internet. Route all interactive SSH through a hardened, heavily monitored bastion:

# ~/.ssh/config on the client
Host bastion
    HostName bastion.internal.example.com
    User jsmith

Host prod-*
    ProxyJump bastion

The bastion should have its own MFA enforcement, session recording, and be the only host with an SSH listener reachable from broader internal networks. This directly supports NIST SP 800-53 AC-17 (Remote Access), which requires monitoring and control of remote access paths.


3. Cryptographic Hardening โ€” Ciphers, MACs, Key Exchange

Disable legacy algorithms with known weaknesses (CBC-mode ciphers vulnerable to plaintext-recovery attacks, MD5/SHA-1 based MACs, Diffie-Hellman groups below 2048-bit). This is a direct CIS Benchmark requirement (5.2.11โ€“5.2.13) and supports NIST SP 800-131A cryptographic algorithm transitions.

# /etc/ssh/sshd_config โ€” CIS/NIST-aligned algorithm restriction
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr

MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com

KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256

HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256

Never re-enable for compatibility with legacy clients:

Algorithm class Deprecated / insecure
Ciphers arcfour*, blowfish-cbc, 3des-cbc, *-cbc (CBC mode generally)
MACs hmac-md5*, hmac-sha1 (non-ETM), umac-64*
Key exchange diffie-hellman-group1-sha1, diffie-hellman-group14-sha1, any group < 2048-bit
Host keys ssh-dss (DSA โ€” removed upstream), ssh-rsa with SHA-1 signatures

Verify what a running daemon actually offers:

ssh -Q cipher
ssh -Q mac
ssh -Q kex
nmap --script ssh2-enum-algos -p 22 <host>

Protocol Version

Protocol 2

SSH Protocol 1 is cryptographically broken and removed entirely from modern OpenSSH โ€” confirm no legacy device on the network still negotiates it.


4. Session and Connection Controls

# /etc/ssh/sshd_config
LoginGraceTime 30
MaxAuthTries 3
MaxSessions 4
MaxStartups 10:30:60
ClientAliveInterval 300
ClientAliveCountMax 2
TCPKeepAlive no
  • MaxAuthTries limits credential-guessing attempts per connection (CIS 5.2.16).
  • ClientAliveInterval / ClientAliveCountMax terminate idle sessions (300s x 2 = 10-minute idle timeout) โ€” CIS 5.2.20, and supports NIST SP 800-53 AC-12 (Session Termination).
  • LoginGraceTime bounds how long an unauthenticated connection can hold a slot, mitigating connection-exhaustion DoS.
  • MaxStartups throttles concurrent unauthenticated connections.

Disable Unused Forwarding and Features

X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
GatewayPorts no
PermitTunnel no
PermitUserEnvironment no

Only enable AllowAgentForwarding, AllowTcpForwarding, or X11Forwarding where there's an explicit operational need (e.g., a bastion that legitimately needs AllowTcpForwarding for ProxyJump) โ€” each is additional attack surface for pivoting.

Banner /etc/issue.net

Display an authorized-use warning banner before authentication (CIS 5.2.21) โ€” a legal/administrative control supporting NIST SP 800-53 AC-8 (System Use Notification), not a technical one.


5. Host Key Management

  • Generate host keys only in Ed25519 and RSA (3072+) โ€” remove or don't generate DSA/ECDSA-P256 host keys if not required:
# Remove weak/legacy host keys
rm -f /etc/ssh/ssh_host_dsa_key*
rm -f /etc/ssh/ssh_host_ecdsa_key*

# Regenerate strong host keys if needed
ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ''
ssh-keygen -t rsa -b 3072 -f /etc/ssh/ssh_host_rsa_key -N ''
  • File permissions (CIS 5.2.1โ€“5.2.4): host private keys must be root-owned and unreadable by any other principal.
chown root:root /etc/ssh/ssh_host_*_key
chmod 600 /etc/ssh/ssh_host_*_key
chown root:root /etc/ssh/sshd_config
chmod 600 /etc/ssh/sshd_config
  • Distribute host key fingerprints out-of-band (configuration management, MDM, or a signed known_hosts baseline) so users/clients can detect a host key change indicating a potential MITM rather than blindly accepting ssh-keyscan output.
  • For fleets, consider an SSH Certificate Authority (ssh-keygen -s, or Vault SSH secrets engine, or Smallstep step-ssh) issuing short-lived host and user certificates instead of managing long-lived keys and known_hosts/authorized_keys sprawl.

6. User Key Hygiene

chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
  • StrictModes yes (default) makes sshd refuse logins if ~/.ssh or authorized_keys have loose permissions โ€” don't disable it.
  • Pin authorized_keys entries with command=, from=, and no-port-forwarding restrictions for automation/service keys so a leaked key has bounded blast radius:
command="/usr/local/bin/backup-only.sh",from="10.10.5.20",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-ed25519 AAAA... svc_backup
  • Centralize authorized_keys management (AuthorizedKeysCommand against an internal API, LDAP, or config management) rather than editing files ad hoc across a fleet โ€” this is what makes timely revocation on offboarding actually feasible.

7. Logging, Monitoring, and Brute-Force Protection

# /etc/ssh/sshd_config
LogLevel VERBOSE
SyslogFacility AUTH

VERBOSE additionally logs the key fingerprint used for each authentication, which is essential for tracing which specific key was used in an incident (CIS 5.2.5).

Forward to Central Logging / SIEM

Ship auth.log / journalctl -u sshd to a SIEM with alerting on:

Event Why it matters
Repeated Failed password / Failed publickey from one source Brute-force or credential stuffing
Accepted publickey for a privileged account outside change windows Possible compromised key use
Invalid user attempts Reconnaissance / username enumeration
New key fingerprint accepted for an existing user Unauthorized key addition
Received disconnect immediately after connect at scale Scanning activity

This supports NIST SP 800-53 AU-2 (Audit Events) and AU-6 (Audit Review, Analysis, and Reporting).

Automated Brute-Force Mitigation

Deploy fail2ban (or an equivalent, e.g., sshguard, cloud security-group auto-remediation) to temporarily ban sources with repeated auth failures:

# /etc/fail2ban/jail.local
[sshd]
enabled = true
port = ssh
filter = sshd
maxretry = 4
findtime = 600
bantime = 3600

This is a compensating control, not a substitute for key-based auth and network restriction โ€” treat it as defense-in-depth.


8. Network-Level Considerations

  • Non-standard port: Moving SSH off port 22 reduces automated scanner noise but is not a security control โ€” treat it purely as noise reduction, never as a substitute for the controls above. Document it clearly if used, since it affects firewall rules and monitoring queries.
  • Rate limiting at the firewall/load balancer, independent of fail2ban, catches distributed attempts that stay under a single-host threshold.
  • Disable SSH entirely on hosts that don't need interactive access (many appliances and hardened images ship sshd running by default โ€” turn it off if unused: systemctl disable --now sshd).
  • Segment management-plane SSH access onto a dedicated VLAN/subnet reachable only from the bastion and jump infrastructure, not from general user or production application networks.

Hardening Checklist

Control Priority Verify With
Password authentication disabled Critical sshd -T \| grep -i passwordauthentication
Root login disabled Critical sshd -T \| grep -i permitrootlogin
Public key / certificate auth enforced Critical sshd -T \| grep -i pubkeyauthentication
MFA layered on privileged access High PAM config / AuthenticationMethods
Legacy ciphers/MACs/KEX disabled High ssh -Q cipher/mac/kex + nmap ssh2-enum-algos
Protocol 2 only Critical sshd -T \| grep -i protocol
MaxAuthTries โ‰ค 3โ€“4 Medium sshd -T \| grep -i maxauthtries
Idle session timeout configured Medium ClientAliveInterval/ClientAliveCountMax
AllowUsers/AllowGroups restricts access High sshd -T \| grep -i allow
X11/Agent/TCP forwarding disabled unless required Medium sshd -T \| grep -i forwarding
Host key files 600, owned by root High stat /etc/ssh/ssh_host_*_key
sshd_config 600, owned by root High stat /etc/ssh/sshd_config
~/.ssh 700, authorized_keys 600 High stat ~/.ssh ~/.ssh/authorized_keys
LogLevel VERBOSE, forwarded to SIEM High sshd -T \| grep -i loglevel
Brute-force protection (fail2ban/sshguard) Medium fail2ban-client status sshd
Direct internet exposure eliminated (bastion in place) Critical Network/firewall audit
Warning banner configured Low sshd -T \| grep -i banner
Unused sshd disabled on non-admin hosts Medium systemctl status sshd

Validate the running effective configuration (not just the file, which may have duplicate or overridden directives) with:

sudo sshd -T

NIST SP 800-53 Rev. 5 Control Mapping

Control Title How SSH hardening addresses it
AC-2 Account Management Named accounts only, no shared/root login
AC-3 Access Enforcement AllowUsers/AllowGroups, key-based ACLs
AC-6 Least Privilege Restricted authorized_keys (command=, no-port-forwarding)
AC-8 System Use Notification Pre-auth banner
AC-12 Session Termination ClientAliveInterval/CountMax
AC-17 Remote Access Bastion/jump host, network restriction
IA-2 Identification and Authentication Public key auth, MFA for privileged access
IA-5 Authenticator Management Key rotation, passphrase-protected keys
SC-8 Transmission Confidentiality/Integrity Strong ciphers/MACs, ETM modes
SC-12 Cryptographic Key Establishment Modern KEX (Curve25519, DH group 16/18)
AU-2 / AU-3 Audit Events / Content of Audit Records VERBOSE logging with key fingerprints
AU-6 Audit Review, Analysis, Reporting SIEM forwarding and alerting
SI-4 System Monitoring fail2ban/sshguard, SIEM detection rules

References

The Security Architecture Site โ€” for internal reference use. Back to contents