Default SSH configuration is not secure enough for production. A freshly installed server gets brute-force attempts within minutes.
Key-Based Authentication
Generate Key Pair
# Ed25519 (recommended)
ssh-keygen -t ed25519 -C "your@email.com"
# RSA (wider compatibility)
ssh-keygen -t rsa -b 4096 -C "your@email.com"Copy Public Key
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server
# Or manually
cat ~/.ssh/id_ed25519.pub | ssh user@server 'mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys'Verify Key Permissions
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pubsshd_config Hardening
Edit /etc/ssh/sshd_config:
# Disable password authentication
PasswordAuthentication no
ChallengeResponseAuthentication no
# Disable root login
PermitRootLogin no
# Limit users
AllowUsers deploy admin
# Or by group
AllowGroups ssh-users
# Change default port (reduces noise)
Port 2222
# Protocol 2 only (v1 is insecure)
Protocol 2
# Disable empty passwords
PermitEmptyPasswords no
# Set login grace time
LoginGraceTime 30
# Max auth attempts
MaxAuthTries 3
# Max sessions
MaxSessions 3
# Disable X11 forwarding (if not needed)
X11Forwarding no
# Disable TCP forwarding (if not needed)
AllowTcpForwarding no
# Disable agent forwarding (if not needed)
AllowAgentForwarding no
# Log level
LogLevel VERBOSE
# Idle timeout (5 minutes)
ClientAliveInterval 300
ClientAliveCountMax 0Test and reload:
# Test config syntax
sudo sshd -t
# Reload (don't restart — keep current session!)
sudo systemctl reload sshdAlways keep your current session open while testing. Open a new terminal to verify you can still connect.
fail2ban
Automatically ban IPs after failed login attempts:
sudo apt install fail2ban# /etc/fail2ban/jail.local
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600 # 1 hour
findtime = 600 # 10 minute windowsudo systemctl enable --now fail2ban
# Check status
sudo fail2ban-client status sshd
# Unban an IP
sudo fail2ban-client set sshd unbanip 203.0.113.50Master this topic with hands-on labs
Go beyond reading — build real projects in sandboxed environments with expert video guidance.
Browse Courses →SSH Config (Client Side)
~/.ssh/config simplifies connections:
Host production
HostName 10.0.1.50
User deploy
Port 2222
IdentityFile ~/.ssh/id_ed25519
ServerAliveInterval 60
Host staging
HostName 10.0.2.50
User deploy
Port 2222
IdentityFile ~/.ssh/id_ed25519
# Jump through bastion
Host internal-*
ProxyJump bastion
Host bastion
HostName bastion.example.com
User admin
Port 2222
IdentityFile ~/.ssh/id_ed25519
Host internal-db
HostName 10.0.3.100
User dbadmin
Host internal-app
HostName 10.0.3.101
User deployssh production # Instead of: ssh -p 2222 -i ~/.ssh/id_ed25519 deploy@10.0.1.50
ssh internal-db # Automatically jumps through bastionJump / Bastion Host
Internet → Bastion (public IP) → Internal servers (private IPs)# Direct jump
ssh -J bastion.example.com internal-server
# Multi-hop
ssh -J bastion1,bastion2 internal-serverBastion config — minimal surface:
# On bastion server sshd_config
AllowTcpForwarding yes # Required for proxying
AllowAgentForwarding no
X11Forwarding no
PermitRootLogin no
MaxSessions 10Two-Factor Authentication
sudo apt install libpam-google-authenticator
# Run as the user
google-authenticatorAdd to PAM:
# /etc/pam.d/sshd
auth required pam_google_authenticator.so# /etc/ssh/sshd_config
AuthenticationMethods publickey,keyboard-interactive
ChallengeResponseAuthentication yesNow login requires: SSH key + TOTP code.
Get weekly IT automation tips
Docker, Ansible, Terraform, MLOps — curated insights delivered to your inbox. No spam.
Subscribe Free →Monitoring
# Recent logins
last -n 20
# Failed login attempts
grep "Failed password" /var/log/auth.log | tail -20
# Currently connected
who
w
# Active SSH sessions
ss -tnp | grep sshAudit Checklist
# Check for password auth
grep "PasswordAuthentication" /etc/ssh/sshd_config
# Check for root login
grep "PermitRootLogin" /etc/ssh/sshd_config
# Check authorized_keys for unknown keys
cat ~/.ssh/authorized_keys
# Check for weak host keys
ssh-keygen -lf /etc/ssh/ssh_host_*_key.pub
# Check listening port
ss -tlnp | grep sshdWhat's Next?
Our SELinux for System Admins course covers mandatory access controls that protect SSH and system services. Ansible Automation in 30 Minutes teaches automating SSH hardening across fleets. First lessons are free. -e ---
Ready to go deeper? Explore our hands-on DevOps courses — practical labs covering Docker, Ansible, Terraform, and more.
Ready to learn by doing?
Stop reading tutorials — start building. Expert video courses with hands-on labs in real sandboxed environments.
Related Articles
SSH Key Setup and Hardening Guide
Set up SSH keys and harden your server. Key generation, agent forwarding, config file management, and security tips for remote access.
Kubernetes Pod Security Standards
Kubernetes Pod Security Standards for workload hardening. Privileged, Baseline, and Restricted profiles with YAML and migration tips.
Linux File Permissions Explained
Master Linux file permissions. chmod, chown, umask, SUID, SGID, sticky bit, and ACLs with practical examples for system security.
Steampipe Cloud Infrastructure Queries
Steampipe lets you query AWS, Azure, GCP, Kubernetes, and GitHub using SQL. Learn how to audit cloud resources, check compliance, and build dashboards.
Systemd Service Files Explained
Write systemd service files. Unit configuration, restart policies, dependency ordering, timer units, and debugging Linux services.
Tailscale Zero Trust Networking
Tailscale creates a WireGuard mesh network for zero trust access to servers, Kubernetes clusters, and databases. Learn how Tailscale replaces VPNs.
Explore topics
Browse more articles on the topics covered here.