SSH Hardening and Bastion Host Deployment: Certificate Authentication, Jump Hosts, and sshd_config 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.
Discovered the SSH key sprawl problem at a startup after the third engineer departure in six months. Each time an engineer left, the security checklist said 'remove SSH keys' but there was no centralized record of which servers had which keys, so the off-boarding process consisted of removing the key from the servers the engineer was known to have accessed — not the servers they had accessed during incidents, not the servers they had added themselves during their tenure, not the servers where their key had been copied by another engineer to speed up a deployment. The last audit before migrating to SSH certificates found 47 orphaned public keys across 80 servers.
SSH certificates solved all three problems simultaneously. There are no authorized_keys files to audit; certificates expire automatically and leave no persistent artifact; and the certificate issuance endpoint requires SSO authentication, so access control is centralized in the identity provider rather than distributed across hundreds of server-specific key files. The migration took two days — generating the CA, distributing the TrustedUserCAKeys configuration via Ansible, issuing certificates to the team, and then removing all individual authorized_keys entries.
sshd_config hardening: the security baseline for every SSH server
sshd_config hardening is a one-time configuration change per server that eliminates entire attack surface categories. Disabling password authentication removes brute force as an attack vector. Disabling root login requires named user accountability for privilege escalation. Restricting allowed users to a specific group prevents accidental SSH access grants through account creation. These settings are low-risk to apply and high-impact on the attack surface — deploying them via Ansible across all managed servers takes under an hour.
Use ssh-audit to establish a pre- and post-hardening security score for compliance documentation
Run ssh-audit against each server before applying sshd_config changes to establish a baseline finding list, then run it again after applying the hardening configuration to verify all findings were addressed and generate the post-hardening report. Include the pre- and post-hardening ssh-audit output in the change management documentation for compliance purposes — security auditors frequently ask for evidence that SSH hardening was verified rather than assumed. The most impactful sshd_config changes for improving ssh-audit scores are: adding an explicit KexAlgorithms allowlist with only curve25519-sha256 and diffie-hellman-group16-sha512, adding an explicit Ciphers allowlist with only chacha20-poly1305@openssh.com and aes256-gcm@openssh.com, and adding an explicit MACs allowlist with only hmac-sha2-256-etm@openssh.com and hmac-sha2-512-etm@openssh.com.
Deploy sshd_config changes via Ansible with a pre-flight syntax check to prevent locking yourself out
Deploy sshd_config changes using an Ansible playbook that always runs sshd -t (syntax check mode) before restarting the sshd service, preventing a configuration syntax error from restarting sshd with a broken configuration that locks all users out. The Ansible task sequence: copy the new sshd_config to /etc/ssh/sshd_config.new, run sshd -t -f /etc/ssh/sshd_config.new and fail the playbook if the syntax check returns a non-zero exit code, then atomically move sshd_config.new to sshd_config and restart sshd only if syntax validation passed. Test the playbook against a single non-production server with ansible-playbook ssh-hardening.yml --limit test-server before running across the fleet. Keep an existing SSH session open to the server during the deployment so that if the sshd restart fails or the new configuration introduces an unexpected access issue, you can revert without losing access.
Certificate authority: eliminating authorized_keys sprawl at scale
SSH certificate authentication is the most impactful operational improvement available for environments managing SSH access across more than 20 servers. The transition from authorized_keys to certificates eliminates key sprawl, provides automatic expiry-based access revocation, and enables centralized issuance logging that shows every certificate issued, to whom, and for how long — an audit capability that authorized_keys management cannot match without additional tooling.
Store the SSH CA private key in a hardware security module or Vault to prevent CA key compromise
The SSH CA private key is the master credential for all SSH access in the environment — its compromise allows an attacker to issue certificates for any username to any server trusting the CA. Store the CA private key in HashiCorp Vault's SSH secrets engine or a hardware security module (HSM) rather than as a plain file on a server. With Vault's SSH secrets engine, the CA private key is generated inside Vault and never exposed to operators — certificate signing operations are performed by Vault on behalf of authenticated users. This means compromising the certificate issuance server does not compromise the CA private key, limiting the blast radius of a CA infrastructure compromise to the certificates issued before the compromise is detected rather than unlimited forward access. Configure Vault policies to restrict SSH certificate issuance to operators who have authenticated with MFA and whose OIDC identity asserts the appropriate organizational role.
Set certificate validity to match the access session duration rather than a fixed time window
Configure SSH certificate validity duration based on the expected duration of the access session: 8 hours for standard engineer access (a full workday without re-authentication), 30 minutes for administrative access to production systems (short enough that a leaked certificate has a narrow exploitation window), and 24 hours for automation service accounts that need persistent access without human re-authentication. Use the -V flag in ssh-keygen to set the validity period: -V +8h for 8 hours from now, -V 20260714T090000:20260714T170000 for a specific start and end time. For emergency access to production systems during incidents, issue certificates with extended validity (up to 24 hours) using a break-glass procedure that requires dual approval and is logged in the incident record. The short-lived certificate model means that access revocation for a terminated employee requires only waiting for their current certificate to expire — no authorized_keys file cleanup required across the server fleet.
Briefings like this, every morning before 9am.
Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.
The bottom line
SSH hardening and certificate-based authentication together eliminate the two largest SSH security risks in most enterprise environments: brute force attacks via password authentication and lateral movement via SSH key sprawl. Apply the sshd_config baseline immediately — PasswordAuthentication no, PermitRootLogin no, AllowGroups, and algorithm allowlists — using Ansible with pre-flight syntax validation to prevent lockouts. Deploy an SSH certificate authority (Vault SSH secrets engine or Smallstep CA for SSO integration) to replace authorized_keys with short-lived certificates that expire automatically, eliminating key sprawl and providing centralized issuance logging. Route all SSH access through a bastion host using ProxyJump for centralized access logging, network-level isolation of internal hosts, and a single access revocation point. Run ssh-audit against all servers before and after hardening to generate compliance evidence of the configuration change. The full migration from password authentication to certificate-based bastion access takes two to three days of engineering time and produces an SSH access model where every connection is authenticated, logged, and automatically time-limited.
Frequently asked questions
What are the most important sshd_config settings for SSH hardening?
The highest-impact sshd_config hardening settings in order of security value: PasswordAuthentication no eliminates brute force attacks against SSH by requiring key-based or certificate-based authentication exclusively — this single setting removes the entire password spray attack surface. PermitRootLogin no prevents direct root SSH login, requiring privilege escalation through sudo after authenticating as a named user account, which provides accountability for privileged operations. AllowGroups ssh-users restricts SSH authentication to members of a specific Linux group, so account creation does not automatically grant SSH access — users must be explicitly added to the group. MaxAuthTries 3 limits brute force attempts per connection before disconnection. LoginGraceTime 20 closes unauthenticated connections after 20 seconds, reducing the connection resource from slow-connection attacks. X11Forwarding no and AllowTcpForwarding no disable protocol tunneling that attackers use to create covert channels through the SSH connection. ClientAliveInterval 300 and ClientAliveCountMax 2 close idle sessions after 10 minutes of inactivity, preventing abandoned sessions from providing persistent access.
How do I set up an SSH certificate authority for certificate-based authentication?
Set up an SSH certificate authority by generating a CA key pair on a secure host and configuring SSH servers to trust certificates signed by that CA. Generate the CA key with ssh-keygen -t ed25519 -f /etc/ssh/ca_key -C 'SSH CA for infrastructure' and protect the private key with permissions 600. Distribute the CA public key to all SSH servers by adding TrustedUserCAKeys /etc/ssh/ca_key.pub to sshd_config on each server — this tells sshd to trust any user certificate signed by that CA. To issue a short-lived certificate for a user, sign their public key: ssh-keygen -s /etc/ssh/ca_key -I user@domain.com -n username -V +8h /path/to/user_key.pub which creates user_key-cert.pub valid for 8 hours, valid only for the Linux username specified in -n. The user adds the certificate to their SSH agent with ssh-add user_key and user_key-cert.pub and can then SSH to any server trusting the CA without appearing in any server's authorized_keys file. After the 8-hour window, the certificate expires and the user must re-request a certificate, which can be integrated with SSO authentication for zero-friction certificate issuance.
How do I deploy a bastion host and configure transparent ProxyJump access?
Deploy a bastion host as a hardened EC2 instance (or equivalent) in a public subnet with an Elastic IP, security group allowing SSH only from authorized IP ranges, no other inbound rules, and no other software running on the host. Configure the bastion's sshd_config with AllowTcpForwarding yes and GatewayPorts no to allow port forwarding for the ProxyJump use case while preventing external access to forwarded ports. On internal servers, configure the security group or firewall to accept SSH only from the bastion's private IP, eliminating all direct SSH exposure to the internet. Configure users' SSH client config file (~/.ssh/config) with a Host entry for the bastion and a wildcard Host * entry that routes internal host connections through the bastion: Host bastion.example.com HostName bastion-public-ip User admin and Host 10.0.*.* ProxyJump bastion.example.com. Users then ssh user@10.0.1.100 directly from their laptop — SSH transparently connects through the bastion without requiring the user to manually SSH to the bastion first. Enable SSH session logging on the bastion with sshd's ForceCommand or with tlog to capture all commands executed during bastion sessions for audit purposes.
How do I audit and remediate SSH authorized_keys sprawl across a server fleet?
Audit SSH authorized_keys sprawl across a server fleet by collecting all authorized_keys files from all servers and analyzing which public keys appear and how frequently. Use Ansible to collect authorized_keys from all managed hosts: ansible all -m command -a 'cat /root/.ssh/authorized_keys /home/*/.ssh/authorized_keys 2>/dev/null' > keys_audit.txt, then parse the output to extract unique public key fingerprints with ssh-keygen -l -f key_file for each key. Build a spreadsheet mapping fingerprints to key owners (from onboarding records or by asking engineers to identify their keys), identify keys with no known owner (former employees, decommissioned service accounts), and identify keys that appear on more than 10 servers (high-blast-radius keys that require immediate rotation if compromised). After the audit, revoke unknown-owner keys from all authorized_keys files using Ansible's ansible.posix.authorized_key module with state=absent to remove specific key fingerprints. Establish a quarterly audit process and transition to SSH certificates to prevent future key sprawl — certificates expire automatically and leave no persistent authorized_keys entries.
How do I configure fail2ban to protect SSH from brute force attacks?
Configure fail2ban to block SSH brute force attempts by monitoring the authentication log and banning IPs that exceed a failed login threshold. Install fail2ban and create a jail configuration in /etc/fail2ban/jail.local with the sshd jail: [sshd] enabled = true port = ssh filter = sshd logpath = /var/log/auth.log maxretry = 5 bantime = 3600 findtime = 600. This configuration bans any IP that fails authentication 5 times within a 10-minute window for 1 hour. Increase bantime to 86400 (24 hours) for production servers where brute force protection is critical. Enable the repeat-offender protection by adding recidive jail configuration: bantime = 604800 findtime = 86400 maxretry = 3 using the fail2ban-recidive action which bans IPs that have been banned 3 times within 24 hours for a full week. Monitor fail2ban activity with fail2ban-client status sshd to see the current ban list and fail2ban-client status sshd --verbose for recent ban events. Forward fail2ban ban events to the SIEM by configuring the backend to write to syslog, which your log collector picks up for centralized monitoring.
How do I assess my SSH configuration security using ssh-audit?
Use the ssh-audit tool to assess SSH server configuration security by running it against a target server and reviewing the findings against current cipher, MAC algorithm, and key exchange recommendations. Install ssh-audit with pip install ssh-audit and run it against a server: ssh-audit hostname or ssh-audit --port 22 hostname. The tool connects to the SSH server, retrieves the list of supported algorithms from the server's SSH handshake, and evaluates each algorithm against known weaknesses. The output categorizes findings as CRITICAL (broken algorithms that should be immediately disabled), WARNING (deprecated algorithms that should be disabled when possible), and INFO (acceptable algorithms). Common findings include: diffie-hellman-group14-sha256 (moduli less than 3072 bits, downgrade risk), ecdh-sha2-nistp256 and ecdh-sha2-nistp384 (NIST curves with potential NSA influence, prefer curve25519-sha256), hmac-sha1 and hmac-md5 (weak MAC algorithms). After reviewing findings, remove the flagged algorithms from /etc/ssh/sshd_config by modifying KexAlgorithms, Ciphers, and MACs to explicit allowlists containing only current-generation algorithms.
How do I integrate SSH certificate issuance with OIDC SSO for zero-trust access?
Integrate SSH certificate issuance with your organization's OIDC SSO provider to enable short-lived certificate issuance that requires successful SSO authentication before a certificate is granted. Tools like HashiCorp Vault's SSH secrets engine, Smallstep CA, or Teleport provide this integration natively. With Vault's SSH secrets engine, configure the SSH role with allowed_users templates that map from OIDC claims: a user who authenticates to Vault via OIDC gets a short-lived SSH certificate (default 30 minutes) signed by the Vault-managed CA, with the certificate principal set from the OIDC user claim. The SSH servers are configured with TrustedUserCAKeys pointing to the Vault CA public key. A user who leaves the organization or whose OIDC account is disabled immediately loses the ability to obtain new certificates — their existing certificate expires within 30 minutes, and access is fully revoked. This eliminates both the key sprawl problem (no persistent authorized_keys entries) and the revocation problem (expiry is automatic) at the cost of requiring an active SSO session for every SSH access attempt, which is the intended zero-trust behavior.
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.
