Harden SSH: Key Auth, Fail2ban, and Port Hardening

Khimananda Oli 7 min read Database
Harden SSH: Key Auth, Fail2ban, and Port Hardening

By Khimananda Oli | Last reviewed: August 2026

Leaving default SSH configurations on internet-facing servers is an invitation for compromise, as automated bots scan public IP ranges continuously looking for weak credentials. To properly harden SSH: key auth, Fail2ban, and port hardening form the essential defense triad that stops the vast majority of unauthorized access attempts before they gain a foothold. This guide provides the exact configuration steps I use when performing an initial Ubuntu server setup to ensure production environments remain audit-ready and resilient against brute-force campaigns.

Attacker BotPort HardeningNon-Std PortReduces NoiseFail2banRate LimitingIP BanningKey Auth OnlyNo PasswordsCrypto Verify
Layered defense model: harden SSH with key auth, Fail2ban, and port hardening to filter threats at each stage

How do you configure SSH key authentication and disable passwords?

Password authentication is the single largest attack surface on any SSH daemon. Even complex passwords can fall to credential stuffing or phishing, whereas cryptographic keys provide mathematical proof of identity that cannot be guessed. When I help teams transition from legacy setups, enforcing key-based auth is always step one in any AWS EC2 security baseline.

Generate and deploy Ed25519 keys

Ed25519 keys are smaller, faster, and more resistant to side-channel attacks than RSA. Generate a key pair on your local machine:

ssh-keygen -t ed25519 -C "[email protected]" -f ~/.ssh/id_ed25519_prod

Copy the public key to your server using ssh-copy-id while password auth is still temporarily enabled:

ssh-copy-id -i ~/.ssh/id_ed25519_prod.pub user@server-ip

Harden sshd_config for key-only access

Edit the SSH daemon configuration file. Always validate syntax before restarting to avoid locking yourself out:

sudo nano /etc/ssh/sshd_config

# Critical hardening directives
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
AllowUsers deployer admin
X11Forwarding no
PermitEmptyPasswords no

Validate and apply the configuration safely:

sudo sshd -t && sudo systemctl reload sshd

The AllowUsers directive acts as an explicit whitelist. Even if an attacker obtains valid credentials for another system account, they cannot authenticate via SSH unless listed. This principle of least privilege aligns with ISO 27001 access control requirements and simplifies audit evidence collection.

How do you set up Fail2ban to prevent SSH brute-force attacks?

Fail2ban monitors authentication logs and dynamically updates firewall rules to block offending IP addresses. While key authentication prevents successful breaches, Fail2ban reduces log noise and resource exhaustion from persistent scanners. In my experience managing SOC 2 compliant infrastructure, this tool is essential for demonstrating active intrusion prevention controls.

Install and configure jail.local

Never edit jail.conf directly; package updates will overwrite it. Create a local override:

sudo apt install fail2ban -y
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local

Configure the SSH jail with aggressive but practical thresholds:

[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
findtime = 600
bantime = 3600
banaction = nftables-multiport

Note that the port value must match your actual SSH port. If you changed SSH to 2222, Fail2ban must monitor that same port or it will watch the wrong service entirely. The nftables-multiport action is preferred over iptables on modern Ubuntu and Debian releases for better performance and compatibility.

Verify Fail2ban is actively protecting SSH

Check jail status and currently banned IPs:

sudo fail2ban-client status sshd
sudo fail2ban-client get sshd bantime

For persistent bans across reboots, ensure the Fail2ban service is enabled:

sudo systemctl enable --now fail2ban
auth.logFailed AttemptsFail2ban EnginePattern MatchCount > maxretryWithin findtimenftables RuleDROP Source IPOK
Fail2ban processing flow: logs trigger pattern matching which enforces temporary firewall bans automatically

Should you change the default SSH port for security?

Changing the default SSH port from 22 to a non-standard value like 2222 or 2200 does not stop targeted attackers who perform full port scans. However, it dramatically reduces log volume from opportunistic bots that only scan well-known ports. In practice, this makes genuine security alerts easier to spot and reduces storage costs for log aggregation services.

Change SSH port safely without lockout

Before modifying the SSH port, ensure your cloud provider's security group or firewall allows the new port. On AWS, update the EC2 security group first. Then modify the daemon config:

# In /etc/ssh/sshd_config
Port 2222

# Keep port 22 temporarily during transition
Port 22
Port 2222

Reload SSH and test connectivity on the new port before closing your current session:

sudo sshd -t && sudo systemctl reload sshd
ssh -p 2222 user@server-ip

Only after confirming the new port works should you remove the legacy Port 22 line and reload again. Update Fail2ban's jail configuration to match. For teams managing multiple servers, codifying this in Terraform or Ansible prevents configuration drift and ensures consistency across environments.

What additional SSH hardening measures complement key auth and Fail2ban?

Key authentication and Fail2ban address authentication and rate limiting, but comprehensive hardening requires addressing protocol-level weaknesses and operational hygiene. These measures are particularly relevant for organizations pursuing SOC 2 or ISO 27001 certification where auditors examine defense-in-depth strategies.

Hardening MeasurePurposeImplementation EffortAudit Relevance
Disable root loginPrevent direct privileged accessLowHigh — required by most frameworks
Restrict cipher suitesRemove weak encryption algorithmsMediumHigh — cryptographic compliance
Enable MFA via PAMAdd second factor for sensitive hostsMediumHigh — MFA requirement
SSH certificate authCentralized key lifecycle managementHighVery High — scalable compliance
Bastion/Jump hostSingle ingress point for auditingMediumHigh — network segmentation

Restrict ciphers to modern, secure options only:

Ciphers [email protected],[email protected],[email protected]
MACs [email protected],[email protected]
KexAlgorithms curve25519-sha256,diffie-hellman-group16-sha512

These settings eliminate legacy algorithms vulnerable to known attacks while maintaining compatibility with all modern SSH clients. Test thoroughly with your team's tooling before deploying broadly.

Default SSH Config✗ Password auth enabled✗ Root login permitted✗ Port 22 exposed✗ No rate limiting✗ Legacy ciphers accepted✗ All users can SSHRisk: HIGH | Audit: FAILHardened SSH Config✓ Ed25519 key-only auth✓ Root login disabled✓ Non-standard port✓ Fail2ban active✓ Modern ciphers only✓ AllowUsers whitelistRisk: LOW | Audit: PASS
Default versus hardened SSH comparison: implementing key auth, Fail2ban, and port hardening transforms security posture

How do you verify SSH hardening is working correctly?

Configuration without verification is just hope. After applying changes, run these validation checks to confirm each hardening layer functions as intended:

  1. Test key authentication: Attempt SSH from a machine without the private key. Connection must be refused immediately without prompting for a password.
  2. Verify Fail2ban triggers: Intentionally fail authentication three times from a test IP. Confirm the IP appears in fail2ban-client status sshd output within seconds.
  3. Confirm port restriction: Scan the server with nmap -p 22,2222 server-ip. Only the configured port should show as open.
  4. Audit cipher negotiation: Run ssh -vvv user@server and inspect the debug output to confirm only approved ciphers and MACs are negotiated.
  5. Check log cleanliness: Review /var/log/auth.log after 24 hours. Failed attempt volume should drop significantly compared to pre-hardening baselines.

Automate these checks using tools like ssh-audit or integrate them into your CI/CD pipeline if you manage infrastructure as code. Continuous verification ensures hardening persists through updates and configuration changes.

Secure Your Servers With Confidence

Implementing these measures to harden SSH: key auth, Fail2ban, and port hardening eliminates the most common attack vectors targeting Linux servers. The combination of cryptographic authentication, automated threat response, and reduced visibility creates a defense posture that satisfies both security best practices and compliance auditor expectations. Start with key authentication today, add Fail2ban this week, and evaluate port changes during your next maintenance window. If your team needs hands-on support securing cloud infrastructure or preparing for SOC 2 audits, reach out to discuss your specific environment.

Frequently Asked Questions

Set PasswordAuthentication to no and ChallengeResponseAuthentication to no in /etc/ssh/sshd_config. Validate syntax with sshd -t before restarting sshd.service to prevent lockout. Always maintain an active session while testing new key-based access from a separate terminal window.

Start with bantime = 1h and findtime = 10m in jail.local for balanced protection. Increase to 24h or use incremental bans via recidive jail for persistent attackers. Monitor fail2ban-client status sshd logs to tune thresholds without blocking legitimate administrators during maintenance windows.

Yes, moving SSH to a non-standard port like 2222 reduces automated scanner noise significantly. It provides security through obscurity but does not replace key authentication or firewall rules. Configure your cloud provider security groups and fail2ban filters to match the new port immediately.

Use Ed25519 keys generated via ssh-keygen -t ed25519 for modern servers. They offer better performance and stronger cryptography than RSA-4096. Ensure OpenSSH 8.0 or later supports this algorithm on both client and server sides before deploying across production infrastructure.

Yes, configure Fail2ban banaction to use AWS CLI, gcloud, or Azure CLI commands instead of iptables-multiport. This blocks attackers at the network edge before traffic reaches your instance. Update jail.local with appropriate API credentials and region-specific firewall rule names for effective integration.

Keep your current SSH session open and test configuration changes in a new terminal. Use sshd -t to validate syntax before applying. Configure console access through your cloud provider as a backup recovery method. Never restart sshd until key authentication is confirmed working separately.

Set .ssh directory to 700 and authorized_keys file to 600 ownership by the connecting user. Incorrect permissions cause silent authentication failures even with valid keys. Run chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys after adding new public keys to ensure proper access control enforcement.

AllowUsers creates an explicit whitelist rejecting all unlisted accounts automatically. DenyUsers blocks specific accounts while permitting everyone else. Prefer AllowUsers for production servers to enforce least privilege. Combine with group-based AllowGroups for scalable team management without editing individual user entries repeatedly.

Disable PermitRootLogin completely and create named admin accounts with sudo privileges. Direct root access eliminates audit trails and increases compromise impact. If legacy scripts require root, use PermitRootLogin forced-commands-only to restrict execution to specific approved commands listed in the authorized_keys file options field.

Rotate SSH keys quarterly or immediately after staff departures. Automate rotation using Ansible or Terraform to distribute new public keys and remove old entries atomically. Maintain key inventory tracking expiration dates. Never reuse keys across environments or share private keys between team members under any circumstances.

Unblock immediately using fail2ban-client set sshd unbanip YOUR_IP_ADDRESS. Add trusted IPs to ignoreip whitelist in jail.local to prevent recurrence. Check maxretry and findtime values if false positives occur frequently. Consider implementing allowlisting via CIDR notation for office networks or VPN endpoints used by administrators.

Yes, run semanage port -a -t ssh_port_t -p tcp NEWPORT on RHEL-based systems with SELinux enforcing. Without this policy update, sshd fails to bind despite correct sshd_config settings. Verify with ss -tlnp | grep NEWPORT after restart. Ubuntu systems typically do not require equivalent AppArmor adjustments for port changes.

Use ssh-audit tool or OpenSCAP profiles to scan live configurations against CIS benchmarks. Integrate checks into CI pipelines using test-kitchen or Molecule for infrastructure code validation. Schedule weekly automated scans reporting deviations. Compare results against documented baseline configurations stored in version control for drift detection and remediation tracking.

No.

No.