How to Harden SSH Server Configuration for Linux Security

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.
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.
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
Free resources
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.
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.
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.

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.
Win a $2,495 Black Hat pass.
Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.
