In March 2026, a fintech company lost access to 14 production servers after attackers brute-forced an SSH daemon left running on port 22 with password authentication enabled — a misconfiguration flagged in their last audit but never remediated. This guide walks through exactly what you need to lock down SSH on a production Linux server, with real commands and output you can verify yourself.
Audit Your Current SSH Configuration First
Before changing anything, know what you’re working with. Run a quick audit against your own server using ssh-audit — a tool that fingerprints your SSH daemon and flags weak algorithms, deprecated key exchange methods, and policy violations.
$ ssh-audit 192.0.2.45
# general
(gen) banner: SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.6
(gen) software: OpenSSH 8.9p1
(gen) compatibility: OpenSSH 7.4+
# key exchange algorithms
(kex) diffie-hellman-group14-sha1 -- [fail] using broken SHA-1 hash
(kex) curve25519-sha256 -- [info] available
# encryption algorithms (ciphers)
(enc) aes128-cbc -- [fail] using deprecated CBC mode
(enc) chacha20-poly1305@openssh.com -- [info] available
# authentication
(auth) password authentication -- [warn] enabled
(auth) publickey authentication -- [info] enabled
Two immediate red flags: diffie-hellman-group14-sha1 uses a broken SHA-1 hash, and aes128-cbc is vulnerable to BEAST-style attacks. Password authentication is also enabled — that’s your biggest exposure. An attacker with a decent wordlist and hydra can run thousands of attempts per minute against port 22.
What to do next: pull these exact algorithm names and use them to build a clean /etc/ssh/sshd_config. Allowlist only what’s modern, blocklist everything the audit flagged.
Lock Down sshd_config — The Right Settings for 2026
Open /etc/ssh/sshd_config on your server (ssh prod-web01.internal as user deploy, then sudo vi /etc/ssh/sshd_config). Apply the following hardened configuration block:
# /etc/ssh/sshd_config — hardened, prod-web01.internal
# Last updated: 2026-07-10 by ops-team
Port 2297
AddressFamily inet
ListenAddress 192.0.2.45
# Authentication
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
PubkeyAuthentication yes
AuthorizationKeysFile .ssh/authorized_keys
MaxAuthTries 3
LoginGraceTime 20
AllowUsers deploy ansible-runner
# Session controls
ClientAliveInterval 300
ClientAliveCountMax 2
MaxSessions 4
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
PermitUserEnvironment no
# Algorithms — 2026 baseline
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512
# Logging
LogLevel VERBOSE
SyslogFacility AUTH
Let’s break down the decisions that matter most. Port 2297 won’t stop a determined attacker, but it eliminates 90% of automated scanner noise — your logs become readable again. PermitRootLogin no forces every admin to authenticate as a named user first, creating an audit trail. AllowUsers deploy ansible-runner is a hard allowlist — even if an attacker creates a new system user, they cannot SSH in.
The algorithm block is the 2026 baseline. You’re keeping only Curve25519 for key exchange (fast, modern, no known weaknesses), ChaCha20-Poly1305 and AES-256-GCM for encryption (AEAD ciphers — integrity is built in), and ETM MACs only. Drop everything else.
After editing, validate before restarting — don’t lock yourself out:
$ sudo sshd -t
$ echo $?
0
Exit code 0 means no syntax errors. Then: sudo systemctl restart ssh. Keep your existing session open and open a second terminal to test login before closing anything.
Enforce Key-Based Auth and Rotate Old Keys
Password auth is now off. Every user in your AllowUsers list needs an Ed25519 key pair — the current gold standard. RSA keys below 4096 bits should be retired.
Generate a new key on the admin workstation (not the server):
$ ssh-keygen -t ed25519 -C "deploy@prod-web01.internal-2026" -f ~/.ssh/id_ed25519_prod
Generating public/private ed25519 key pair.
Enter passphrase (empty for no passphrase): ************
Your identification has been saved in /home/jsmith/.ssh/id_ed25519_prod
Your public key has been saved in /home/jsmith/.ssh/id_ed25519_prod.pub
The key fingerprint is:
SHA256:mK9vXp2rLqT8nWdYcBs4hJfAeUzOiG1kPlNmCvRx3Yw deploy@prod-web01.internal-2026
Copy it to the server: ssh-copy-id -i ~/.ssh/id_ed25519_prod.pub -p 2297 deploy@192.0.2.45. Then audit what’s already in authorized_keys on the server — old keys from departed team members are a persistent backdoor that most teams never clean up.
$ cat /home/deploy/.ssh/authorized_keys
ssh-rsa AAAAB3NzaC1yc2EAAA... contractor-2024@example.com
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5... jsmith@prod-web01.internal-2026
That RSA key tagged contractor-2024 has no business being there in 2026. Delete it. Build a quarterly key rotation process — add it to your runbook, assign an owner, or it won’t happen.
What To Do Now
Pick one production server and run ssh-audit <your-server-ip> right now. Copy the output, highlight every line marked [fail] or [warn], and open a ticket to fix each one this week. That single audit takes under two minutes and gives you a concrete remediation list — no guessing, no reading through man pages blind. Start there.
