Port 22
The default SSH port: exposed to continuous automated scanning and brute force attempts from botnets; moving SSH to a non-standard port dramatically reduces automated attack noise without being a security control (obscurity, not security)
PubkeyAuth
PubkeyAuthentication: public key authentication uses asymmetric cryptography where the private key never leaves the client; eliminates password brute force entirely since attackers cannot authenticate without the private key material
AllowUsers
The AllowUsers directive limits SSH access to a specific list of usernames: the most critical access restriction directive; combine with AllowGroups to allow an entire group (e.g., sshusers) rather than listing individual users
Ed25519
The recommended SSH key algorithm: uses elliptic curve cryptography over Curve25519 with 128-bit security equivalent; faster and more secure than RSA-2048 or DSA; generate with: ssh-keygen -t ed25519 -C comment

SponsoredRetool

Retool's new app builder is where AI-generated code ships safely

Building apps with AI is easy. Getting them to production safely is another story.

Start building for free today

Internet-facing Linux servers receive SSH brute force attempts within minutes of exposure. The default sshd_config allows password authentication and root login: configurations that attackers rely on. SSH hardening eliminates these attack vectors: key-only authentication makes password brute force mathematically infeasible, AllowUsers restricts which accounts can connect, and cipher suite hardening removes weak cryptographic algorithms that could be downgrade-attacked. This guide covers the specific sshd_config changes that matter and the reasoning behind each.

Disable Password Authentication and Root Login

The two most impactful SSH hardening changes are disabling password authentication and disabling root login. Edit /etc/ssh/sshd_config:

PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM yes
AuthenticationMethods publickey

Do not set PermitRootLogin without-password: this still allows root login with a key, which means a stolen root key gives an attacker direct root shell. Set to no and require sudo escalation from a named user account so all privileged actions are attributable. Before disabling password authentication, verify at least one user has a working public key in ~/.ssh/authorized_keys and that you can authenticate with it from a separate terminal session: do not close your existing session until confirmed. Restart sshd after changes: systemctl restart sshd. Verify the config parses without errors first: sshd -t (test mode, exits 0 on success).

Restrict User and Network Access

Limit which accounts can SSH and from where:

AllowUsers deploy ops-user
AllowGroups sshusers
DenyUsers root administrator
ListenAddress 10.0.1.50
PermitEmptyPasswords no
MaxAuthTries 3
LoginGraceTime 30

AllowUsers and AllowGroups are additive: a user matches if they appear in either list. ListenAddress binds SSH to a specific interface: if you have a management network interface (e.g., 10.0.1.50), bind SSH exclusively to that interface rather than all interfaces (0.0.0.0). This prevents SSH from being reachable on the public-facing interface at all, even with firewall rules. MaxAuthTries 3 limits authentication attempts per connection to 3: after 3 failures, the connection drops. This does not prevent brute force across multiple connections but combined with fail2ban makes distributed brute force impractical. LoginGraceTime 30 disconnects unauthenticated connections after 30 seconds, reducing the connection-holding attack surface.

Free daily briefing

Briefings like this, every morning before 9am.

Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.

Harden Cryptographic Algorithms

Remove weak ciphers, MACs, and key exchange algorithms from sshd_config:

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
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256

These settings remove: 3DES (vulnerable to Sweet32 birthday attack), RC4 (broken stream cipher), arcfour (RC4 alias), DES, HMAC-MD5 (collision-vulnerable), HMAC-SHA1, and small DH groups (diffie-hellman-group1-sha1 and group14). Verify supported algorithms on your OpenSSH version: ssh -Q cipher, ssh -Q mac, ssh -Q kex. Some older OpenSSH versions do not support chacha20-poly1305: check version first with ssh -V. Audit client compatibility: if you have legacy automation or SFTP clients using weak ciphers, they will fail after this change. Run grep -r 'sftp\|ssh' /etc/cron and check automation scripts before deploying.

Configure Session and Forwarding Controls

Disable features that expand the SSH attack surface:

X11Forwarding no
AllowTcpForwarding no
AllowStreamLocalForwarding no
GatewayPorts no
PermitTunnel no
TCPKeepAlive yes
ClientAliveInterval 300
ClientAliveCountMax 2

AllowTcpForwarding no prevents SSH tunnels that bypass network controls: attackers use SSH port forwarding to proxy traffic through compromised hosts. Only enable on bastion/jump hosts that explicitly need forwarding. X11Forwarding no eliminates the X11 protocol attack surface (X11 forwarding has a history of security issues). ClientAliveInterval 300 and ClientAliveCountMax 2 disconnect idle sessions after 10 minutes (300 seconds × 2 checks): unattended privileged SSH sessions are a significant risk if a workstation is stolen or screen-locked. GatewayPorts no prevents remote port forwarding from binding to non-loopback addresses on the server, which would allow the server to proxy traffic for external clients.

Enable Comprehensive SSH Logging

SSH generates authentication events to syslog. Ensure verbose logging:

SyslogFacility AUTH
LogLevel VERBOSE

LogLevel VERBOSE captures the public key fingerprint used for authentication: critical for forensics when you need to identify which key authenticated a suspicious session. Without VERBOSE, the log shows user-authenticated but not which key. Authentication log location: /var/log/auth.log (Debian/Ubuntu) or /var/log/secure (RHEL/CentOS). Forward to your SIEM via syslog or Filebeat. Key log lines to alert on: Failed password (password auth attempt: should be zero if properly configured), Invalid user (username enumeration or brute force), Connection closed by authenticating user root (root login attempt). Create a fail2ban rule to block IPs generating repeated Failed publickey (key spray) or Invalid user entries:

[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 5
bantime = 3600
findtime = 600

Configure an SSH Bastion Host

For environments with multiple servers, a bastion host (jump server) centralizes SSH access through a single hardened entry point. All production servers block SSH from every IP except the bastion's IP. Users SSH to the bastion first, then jump to internal hosts:

# Client ~/.ssh/config
Host bastion
  HostName bastion.example.com
  User ops-user
  IdentityFile ~/.ssh/id_ed25519
  ServerAliveInterval 60

Host prod-*
  ProxyJump bastion
  User ops-user
  IdentityFile ~/.ssh/id_ed25519

On the bastion sshd_config, enable forwarding: AllowTcpForwarding yes and AllowAgentForwarding no (prevent agent forwarding which could expose keys to the bastion host). Use agent forwarding only if the bastion is hardened and trusted: ForwardAgent yes on the client allows the bastion to use your local key material for onward hops, which is convenient but means a compromised bastion can authenticate as you to downstream hosts. Record all bastion sessions with a terminal recorder: options include sshrecorder, tlog (Red Hat), or CyberArk PSM for SSH. Send bastion auth logs to your SIEM with highest retention priority.

The bottom line

The minimum SSH hardening configuration that eliminates credential brute force: PasswordAuthentication no, PermitRootLogin no, AuthenticationMethods publickey, and AllowUsers restricted to named accounts. Add cipher suite hardening (Ciphers, MACs, KexAlgorithms) to remove weak cryptographic algorithms. Configure ClientAliveInterval 300 and ClientAliveCountMax 2 to disconnect idle privileged sessions after 10 minutes. Centralize access through a bastion host with all production server firewall rules blocking SSH from any IP except the bastion.

Frequently asked questions

What are the most important sshd_config settings for SSH hardening?

The five most critical sshd_config settings are: PasswordAuthentication no (eliminates brute force), PermitRootLogin no (prevents direct root compromise), AllowUsers or AllowGroups with specific accounts (limits who can connect), MaxAuthTries 3 (limits failures per connection), and ClientAliveInterval 300 with ClientAliveCountMax 2 (disconnects idle sessions after 10 minutes). These five settings together eliminate the most common SSH attack vectors. Cipher suite hardening adds defense in depth but the authentication controls above have the highest immediate impact.

How do I test my sshd_config changes before applying them?

Test the configuration syntax with 'sshd -t' before restarting: this exits with code 0 if the config is valid and prints errors if not. Always verify a working SSH session using the new configuration before closing your current session: open a second terminal and connect to the server with the new configuration active. Run 'systemctl reload sshd' instead of restart when possible: reload applies the new config without dropping existing connections. Keep at least one terminal session open as a fallback until you confirm the new authentication method works correctly.

How do I enforce SSH key-based authentication and disable password login?

Two-step process: (1) Deploy SSH public keys for all users who need access — append authorized public keys to ~/.ssh/authorized_keys on the server (or use AuthorizedKeysFile in sshd_config to point to a different location). Verify key-based login works before proceeding. (2) Disable password authentication in /etc/ssh/sshd_config: set 'PasswordAuthentication no', 'ChallengeResponseAuthentication no', and 'UsePAM no' (or 'KbdInteractiveAuthentication no' on newer OpenSSH). Reload sshd. Test from a new terminal session — if locked out, use the cloud console (AWS Session Manager, Azure Serial Console) for recovery. For fleet deployments: push the sshd_config change via Ansible or your configuration management tool, verify key deployment first, then apply the password restriction in a second playbook run.

How do I rotate SSH host keys and what are the security implications?

SSH host keys authenticate the server to clients: when clients first connect, they store the server's public key in ~/.ssh/known_hosts and verify it matches on subsequent connections. Rotating host keys: generate new keys with 'ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key' (Ed25519 is the recommended modern algorithm), update sshd_config HostKey directives, and reload sshd. After rotation, all clients connecting for the first time post-rotation will see 'REMOTE HOST IDENTIFICATION HAS CHANGED' warnings — this is expected and not an attack. Distribute the new host key fingerprints to users before rotating. Host key rotation is required after a server compromise (the attacker may have stolen host private keys for future MITM attacks) and recommended annually or when key algorithms are deprecated.

What SSH cipher and algorithm settings should I configure for modern security?

Modern SSH cipher hardening in sshd_config: KexAlgorithms (key exchange) — use curve25519-sha256,ecdh-sha2-nistp521,ecdh-sha2-nistp384 (remove diffie-hellman-group* legacy algorithms). Ciphers — use chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com (remove aes128-cbc, 3des-cbc). MACs — use hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com (remove hmac-md5*, hmac-sha1). HostKeyAlgorithms — use ssh-ed25519,rsa-sha2-512,rsa-sha2-256 (remove ssh-dss, ssh-rsa with SHA1). After applying, test compatibility with all clients before enforcing. The Mozilla SSH hardening guide (infosec.mozilla.org/guidelines/openssh) provides maintained cipher lists updated for current threats.

How do I detect and respond to unauthorized SSH key additions to authorized_keys files across a Linux fleet?

Unauthorized entries in authorized_keys files are a persistent backdoor technique: attackers add their own public key after initial compromise so they retain access even after password rotation. Detect changes using auditd: add a watch rule 'auditctl -w /root/.ssh/authorized_keys -p wa -k ssh_key_change' and the same for each user's home directory. Auditd generates Event ID 4 (file modification) entries in /var/log/audit/audit.log that your SIEM can ingest. For fleet-wide detection, run a scheduled job via your configuration management tool (Ansible, Puppet, Chef) that collects all authorized_keys file contents and hashes, stores them centrally, and alerts on changes that don't correspond to a change management event. Deploy Aide (Advanced Intrusion Detection Environment) for file integrity monitoring: 'aide --init' creates a baseline database and 'aide --check' compares current state against it -- schedule the check in cron and send output to your SIEM. For response: revoke any unrecognized key immediately by removing the entry, rotate the account credentials, and investigate the timeline of access using auth.log entries tied to that key fingerprint (visible with LogLevel VERBOSE in sshd_config).

Sources & references

  1. OpenSSH sshd_config Manual Page
  2. CIS Ubuntu Linux Benchmark - SSH Section
  3. NIST IR 7966: Security of Interactive and Automated Access Management Using SSH

Free resources

25
Free download

Critical CVE Reference Card 2025–2026

25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.

No spam. Unsubscribe anytime.

Free download

Ransomware Incident Response Playbook

Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.

No spam. Unsubscribe anytime.

Free newsletter

Get threat intel before your inbox does.

50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.

Unsubscribe anytime. We never sell your data.

Eric Bang
Author

Founder & Cybersecurity Evangelist, Decryption Digest

Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.

Black Hat Giveaway

Win a $2,495 Black Hat pass.

Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.

Joins Decryption Digest daily briefing. Unsubscribe anytime.

Related Questions: Answer Hub

Giveaway: Black Hat USA 2026 Full-Access Pass ($2,495 value)

Details →
Daily Briefing

Subscribe to enter the giveaway

Every subscriber is automatically entered. You also get daily threat intel every morning: zero-days, ransomware, and nation-state campaigns. Free. No spam.

Already subscribed? You're already entered.

Giveaway

Win a $2,495 Black Hat USA 2026 pass.