The default OpenSSH configuration is designed to maximise compatibility, not security. On a freshly provisioned server it accepts password authentication, supports legacy key exchange algorithms, and places no restrictions on which users can connect. This is reasonable for getting started โ it's unreasonable for a production system reachable from the public internet.
This post walks through the complete hardening checklist we apply to every server in a 47Network Studio engagement. The changes range from one-liners that take seconds to apply, to architectural decisions about SSH access management at team scale.
1. Key-only authentication โ the non-negotiable baseline
Password authentication should be disabled on any server exposed to the internet. Automated scanners probe every IP constantly; a server with password auth enabled will be under brute-force attack within minutes of being provisioned. Set these in /etc/ssh/sshd_config:
PasswordAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
UsePAM no # Only if you don't need PAM for other auth methods
Before disabling passwords: ensure your public key is in ~/.ssh/authorized_keys and you can successfully authenticate with it in a separate terminal. Locking yourself out of a remote server is one of the most recoverable errors that still wastes an hour.
2. Key algorithm hardening
OpenSSH supports key types with widely varying security properties. Restrict to modern, well-analysed algorithms:
# Preferred key types for new keys (generate with: ssh-keygen -t ed25519)
PubkeyAcceptedAlgorithms ssh-ed25519,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,rsa-sha2-512,rsa-sha2-256
# Key exchange algorithms โ remove DH groups and curves with known issues
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512
# MACs โ disable deprecated HMAC algorithms
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
# Ciphers โ disable RC4, DES, CBC mode ciphers
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
On key types: Use Ed25519 for new keys โ it's faster, more compact, and not susceptible to poor random number generation the way ECDSA is. If you need RSA for compatibility with older systems, use 4096-bit. Remove any DSA or ECDSA (P-256/P-384) keys from authorized_keys. ssh-audit (the tool, not the service) will scan your server and identify exactly which algorithms to remove.
3. Restrict access by user and network
Even with key auth, limit who can connect and from where:
# Only allow specific users to SSH
AllowUsers deploy admin alice bob
# Or restrict to a group
AllowGroups ssh-users
# Restrict root login
PermitRootLogin no # or 'prohibit-password' if you need root key auth
# If your servers have a known management IP range
# (best applied per-host in /etc/ssh/sshd_config.d/management.conf)
# Use firewall rules instead โ they're enforced before SSH even sees the packet
Firewall restrictions belong in your firewall rules (iptables/nftables/ufw/cloud security groups), not only in sshd_config โ the firewall acts before SSH, so a misconfigured SSH daemon can't accidentally allow a connection that should be blocked. On cloud providers, use security groups to restrict port 22 (or your chosen port) to your office IPs and VPN egress IPs only.
4. Session and timeout settings
# Disconnect idle sessions
ClientAliveInterval 300 # Send keepalive every 5 minutes
ClientAliveCountMax 2 # Disconnect after 2 missed keepalives (10 min idle)
# Limit login attempt window
LoginGraceTime 20 # 20 seconds to authenticate (default: 120)
MaxAuthTries 3 # Lock out after 3 failed attempts per connection
MaxSessions 4 # Limit multiplexed sessions per connection
# Disable unused features
X11Forwarding no
AllowTcpForwarding no # Unless you specifically need SSH tunnels
AllowStreamLocalForwarding no
PermitTunnel no
GatewayPorts no
5. fail2ban: automated brute-force protection
Even with password auth disabled, scanners will generate noise in your logs and waste connection slots. fail2ban bans IPs that exceed connection attempt thresholds:
# /etc/fail2ban/jail.d/ssh.conf
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
backend = systemd # If using journald
maxretry = 3
findtime = 600 # 10-minute window
bantime = 3600 # 1-hour ban (use -1 for permanent)
ignoreip = 127.0.0.1/8 192.168.0.0/24 # Never ban local networks
For higher-security environments: set bantime = -1 (permanent bans) and manage an allowlist in ignoreip. Combine with a firewall-level geographic restriction if your servers are only accessed from specific countries. fail2ban with aggressive settings significantly reduces log noise and slightly reduces attack surface, but key-only auth is the real protection.
6. The complete hardened sshd_config
A full production-ready config for a Ubuntu 24.04 server:
# /etc/ssh/sshd_config โ hardened configuration
# Test changes with: sshd -t
# Apply changes with: systemctl reload sshd
# โโ Network โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Port 22 # Or a non-standard port; see note below
AddressFamily inet # IPv4 only (remove if you need IPv6)
ListenAddress 0.0.0.0
# โโ Authentication โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
PermitRootLogin no
StrictModes yes
MaxAuthTries 3
MaxSessions 4
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
PasswordAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
UsePAM yes # Keep for system account management
AuthenticationMethods publickey
# โโ User restrictions โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
AllowGroups ssh-users
# โโ Session โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
LoginGraceTime 20
ClientAliveInterval 300
ClientAliveCountMax 2
TCPKeepAlive no # Use SSH-level keepalives, not TCP
# โโ Features (disable unused) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
X11Forwarding no
AllowTcpForwarding no
AllowStreamLocalForwarding no
PermitTunnel no
GatewayPorts no
PermitUserEnvironment no
AllowAgentForwarding no # Allow on bastion hosts
# โโ Logging โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
LogLevel VERBOSE # Logs fingerprint of authenticating key
SyslogFacility AUTH
# โโ Cryptography โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
PubkeyAcceptedAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256
7. Non-standard ports: worth it or security theatre?
Moving SSH to a non-standard port (e.g., 2222 or a random high port) eliminates the lowest-effort opportunistic scanners that only probe port 22. It doesn't stop any targeted attacker โ they'll port-scan you first. The tradeoff: you eliminate 99% of log noise and brute-force attempts, at the cost of slightly more complex SSH commands and client configuration.
Our recommendation: use a non-standard port on internet-facing servers, document it in your team's SSH config file (~/.ssh/config), and combine with firewall IP allowlisting. It's not security โ it's noise reduction that lets you see real threats more clearly in your logs.
8. For teams: Teleport over long-lived SSH keys
If you manage SSH access for a team of more than three engineers, long-lived keys in authorized_keys files are a liability. When someone leaves, you need to hunt down every server they had access to. There's no centralised audit trail of who connected when and what they did.
Teleport replaces static authorized_keys with short-lived certificates issued by your identity provider. Engineers authenticate with their SSO credentials (we integrate it with Keycloak in every Studio zero-trust deployment), receive a certificate valid for their working session, and all sessions are recorded. When someone leaves, you revoke their IdP account and their SSH access disappears automatically โ no authorized_keys cleanup required.
This is the approach we deploy in our zero-trust infrastructure engagements โ Teleport for SSH and Kubernetes access, Pomerium for web app access, all gated through 47ID (Keycloak).