Overview

SSH (Secure Shell) is the core channel for Linux operations and a primary target for attackers. A misconfigured SSH service can lead to a full server compromise. This article covers SSH security best practices comprehensively — from authentication mechanisms, server-side hardening, bastion host architecture, and tunneling techniques to audit logging and brute-force protection.

Key-Based Authentication

Generating Key Pairs

# Generate Ed25519 key (recommended)
$ ssh-keygen -t ed25519 -C "admin@sre.wang" -f ~/.ssh/id_ed25519

# Generate RSA key (for compatibility, at least 4096 bits)
$ ssh-keygen -t rsa -b 4096 -C "admin@sre.wang" -f ~/.ssh/id_rsa

# Generate a passphrase-protected key
$ ssh-keygen -t ed25519 -N "strong-passphrase" -f ~/.ssh/id_ed25519

# View key fingerprint
$ ssh-keygen -lf ~/.ssh/id_ed25519.pub
256 SHA256:abc123... admin@sre.wang (ED25519)

Key Algorithm Comparison

AlgorithmKey LengthSecurityPerformanceRecommendation
Ed25519256 bitVery highVery fastFirst choice
RSA-40964096 bitHighMediumCompatibility scenarios
RSA-20482048 bitMediumFastNot recommended
ECDSA256/384/521HighFastControversial (NIST curves)
DSA1024 bitLow-Deprecated

Deploying Public Keys

# Method 1: ssh-copy-id (recommended)
$ ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server

# Method 2: Manual deployment
$ cat ~/.ssh/id_ed25519.pub | ssh user@server "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

# Method 3: Batch deployment
$ for host in web-{01..10}; do
    ssh-copy-id -i ~/.ssh/id_ed25519.pub admin@$host
done

authorized_keys Permission Control

# Correct permissions
~/.ssh/           → 700
~/.ssh/authorized_keys → 600
Private key file  → 600

# Restrict public key permissions in authorized_keys
# Restrict source IP
from="10.0.0.0/8" ssh-ed25519 AAAAC3... admin@sre.wang

# Restrict command
command="/usr/bin/rsync --server" ssh-ed25519 AAAAC3... backup@sre.wang

# Restrict port forwarding
no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-ed25519 AAAAC3... user@sre.wang

# Combined restrictions
from="10.0.0.0/8",command="/usr/bin/backup.sh",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-ed25519 AAAAC3... backup@sre.wang

Certificate Authentication

Problems with Traditional Key Authentication

  • Each server requires the user’s public key to be deployed individually
  • Key revocation is difficult (must delete from each server)
  • No way to set key expiration
  • Key management lacks auditing

SSH Certificate Authentication Architecture

                    [CA Private Key]
                   /          \
          [Sign User Cert]    [Sign Host Cert]
                |                  |
        [User Holds Cert]  [Server Configures Trusted CA]
                |                  |
           [SSH Connection] → [Server Verifies Cert Signature] → [Login Success]

Setting Up SSH CA

# 1. Generate CA key pair on the CA server
$ ssh-keygen -t ed25519 -f ~/.ssh/ca_user_key -C "SSH User CA"
# Do not set a passphrase (needed for automated signing)

# 2. Sign user certificate
$ ssh-keygen -s ~/.ssh/ca_user_key \
    -I "admin_user" \
    -n admin,root \
    -V +1d \
    ~/.ssh/id_ed25519.pub
# -I: Certificate identifier (for auditing)
# -n: Allowed usernames
# -V: Validity period (+1d = 1 day)

# Certificate file generated: ~/.ssh/id_ed25519-cert.pub

# 3. Configure trusted CA on target servers
$ cat ~/.ssh/ca_user_key.pub >> /etc/ssh/user_ca.pub
# Add to /etc/ssh/sshd_config:
# TrustedUserCAKeys /etc/ssh/user_ca.pub

# 4. Restart sshd
$ systemctl restart sshd

Host Certificates (No More known_hosts Confirmation)

# 1. Generate host key pair on the server (if not already present)
$ ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ""

# 2. Sign host certificate with CA
$ ssh-keygen -s ~/.ssh/ca_user_key \
    -I "web01.sre.wang" \
    -h \
    -n web01.sre.wang,web01 \
    -V +52w \
    /etc/ssh/ssh_host_ed25519_key.pub

# 3. Configure sshd to use host certificate
# /etc/ssh/sshd_config
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub

# 4. Configure client to trust CA
# ~/.ssh/known_hosts or /etc/ssh/ssh_known_hosts
@cert-authority *.sre.wang ssh-ed25519 AAAAC3...

Certificate Revocation

# Create revocation list
$ cat > /etc/ssh/revoked_keys << 'EOF'
ssh-ed25519 AAAAC3... revoked-key-1
ssh-ed25519 AAAAC3... revoked-key-2
EOF

# Configure in sshd_config
RevokedKeys /etc/ssh/revoked_keys

# Or use KRL (Key Revocation List)
$ ssh-keygen -k -f revoked_krl -s ~/.ssh/ca_user_key ~/.ssh/id_ed25519.pub
# Configure in sshd_config
RevokedKeys /etc/ssh/revoked_krl

sshd_config Hardening

Security Baseline Configuration

# /etc/ssh/sshd_config

# === Protocol and Encryption ===
Protocol 2
HostKey /etc/ssh/ssh_host_ed25519_key
# Disable weak keys
# HostKey /etc/ssh/ssh_host_rsa_key
# HostKey /etc/ssh/ssh_host_ecdsa_key

# Encryption algorithms (allow only strong algorithms)
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
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman16-sha512

# === Authentication ===
PermitRootLogin no              # Disable root direct login
PasswordAuthentication no       # Disable password authentication
KbdInteractiveAuthentication no # Disable keyboard-interactive auth
ChallengeResponseAuthentication no
PubkeyAuthentication yes        # Enable key-based authentication
PermitEmptyPasswords no         # Disallow empty passwords

# CA certificate authentication
TrustedUserCAKeys /etc/ssh/user_ca.pub

# === Access Control ===
AllowUsers admin deploy         # Whitelist users
AllowGroups ssh-users           # Whitelist groups
DenyUsers nobody guest          # Blacklist users

# Source restriction (via Match)
Match Address 10.0.0.0/8
    AllowUsers admin root
    PermitRootLogin yes

# === Session Control ===
MaxAuthTries 3                  # Max authentication attempts
MaxSessions 10                  # Max sessions
MaxStartups 10:30:60            # Concurrent unauthenticated connection limit
LoginGraceTime 30               # Authentication timeout (seconds)

# === Timeout Settings ===
ClientAliveInterval 300         # Keepalive interval (seconds)
ClientAliveCountMax 2           # Max keepalive checks
# 300 × 2 = 600 seconds before disconnecting idle connections

# === Logging ===
LogLevel VERBOSE                # Verbose logging
SyslogFacility AUTHPRIV
PrintMotd no                    # Do not print MOTD
PrintLastLog yes                # Show last login time

# === Forwarding ===
AllowTcpForwarding yes          # Configure as needed
X11Forwarding no                # Disable X11 forwarding
AllowAgentForwarding no         # Disable Agent forwarding
PermitTunnel no                 # Disable tunneling

# === Other ===
UseDNS no                       # Disable DNS reverse lookup (speeds up login)
GSSAPIAuthentication no         # Disable GSSAPI (speeds up login)
Banner /etc/ssh/banner          # Pre-login banner

Configuration Validation and Reload

# Validate configuration syntax (important!)
$ sshd -t

# Reload configuration (without dropping existing connections)
$ systemctl reload sshd
# Or
$ systemctl reload ssh

# Verify active configuration
$ sshd -T | grep -E "permitroot|passwordauth|maxauth"

Safe operation procedure: After modifying sshd_config, always keep your current SSH session open and test the new configuration from a separate terminal. If the configuration is incorrect, you can restore it using the original session.

Security Impact of Each Parameter

ParameterDefaultSecure ValueRisk Description
PermitRootLoginyesnoRoot direct login means full compromise
PasswordAuthenticationyesnoPasswords can be brute-forced
MaxAuthTries63Reduces brute-force window
ClientAliveInterval0300Idle connections can be hijacked
X11ForwardingyesnoX11 protocol has security vulnerabilities
AllowTcpForwardingyesAs neededCan be used to build tunnels bypassing firewalls
UseDNSyesnoDNS reverse lookup delay + DNS spoofing risk

SSH Bastion Host

Bastion Host Architecture

[Operator] → [Bastion/Jump Host] → [Internal Servers]
     SSH              SSH
  (public reachable)  (internal only)
# ~/.ssh/config
Host bastion
    HostName bastion.sre.wang
    User admin
    Port 2222
    IdentityFile ~/.ssh/id_ed25519

Host web-*
    User deploy
    IdentityFile ~/.ssh/id_ed25519
    ProxyJump bastion          # Connect through bastion host
    # Or use the legacy syntax
    # ProxyCommand ssh -W %h:%p bastion

Host web-01
    HostName 10.0.1.10
Host web-02
    HostName 10.0.1.11
Host web-03
    HostName 10.0.1.12

# Usage: directly connect to internal servers
$ ssh web-01
# Actual path: Operator → bastion → web-01

Multi-Level Bastion

# ~/.ssh/config
Host bastion-1
    HostName bastion1.sre.wang
    User admin

Host bastion-2
    HostName 10.0.0.2
    User admin
    ProxyJump bastion-1       # Through first-level bastion

Host internal-db
    HostName 192.168.1.100
    User dba
    ProxyJump bastion-2       # Through second-level bastion

# Usage
$ ssh internal-db
# Path: Operator → bastion-1 → bastion-2 → internal-db

Bastion Host Security Configuration

# Special sshd_config for bastion host
# /etc/ssh/sshd_config

# Restrict bastion to forwarding only, no command execution
Match Group jump-users
    AllowTcpForwarding yes
    PermitTTY no
    ForceCommand /bin/false    # Disallow command execution
    X11Forwarding no
    AllowAgentForwarding no

# Or use a restricted shell
Match Group jump-users
    ForceCommand /usr/local/bin/ssh-jump-menu

Recording Bastion Sessions

# Use tlog to record sessions
$ dnf install tlog
# /etc/sshd_config
ForceCommand /usr/bin/tlog-rec-session

# Or use the script command
# /etc/profile.d/audit.sh
if [ -n "$SSH_CONNECTION" ]; then
    LOGDIR=/var/log/ssh-session
    mkdir -p $LOGDIR
    script -qf $LOGDIR/${USER}_$(date +%Y%m%d_%H%M%S).log
fi

Port Forwarding and Reverse Tunnels

Local Port Forwarding

# Forward local port 8080 to remote server's port 80
$ ssh -L 8080:localhost:80 user@server
# Accessing localhost:8080 = accessing server:80

# Forward to a third machine accessible from the remote server
$ ssh -L 8080:10.0.0.10:80 user@jump-server
# Access 10.0.0.10:80 through jump-server

# Run in background
$ ssh -fNL 8080:localhost:80 user@server

# Configure in ~/.ssh/config
Host tunnel
    HostName server.sre.wang
    User admin
    LocalForward 8080 localhost:80

Remote Port Forwarding (Reverse Tunnel)

# Forward remote server's port 9090 to local port 80
$ ssh -R 9090:localhost:80 user@remote-server
# On remote-server, accessing localhost:9090 = accessing local port 80

# Typical use case: NAT traversal
# Internal machine → public server (reverse tunnel)
$ ssh -fNR 2222:localhost:22 user@public-server
# On the public server: ssh -p 2222 user@localhost connects to the internal machine

# Allow remote port forwarding to bind to all interfaces
# /etc/ssh/sshd_config (on the public server)
GatewayPorts yes

Dynamic Port Forwarding (SOCKS Proxy)

# Create a SOCKS5 proxy
$ ssh -D 1080 user@server
# Local port 1080 acts as a SOCKS5 proxy, traffic forwarded through server

# Use the proxy
$ curl --socks5 127.0.0.1:1080 http://internal-service:8080

# Configure browser SOCKS5 proxy: 127.0.0.1:1080

# Run in background
$ ssh -fND 1080 user@server

Tunnel Keepalive

# ~/.ssh/config
Host tunnel
    HostName server.sre.wang
    User admin
    RemoteForward 2222 localhost:22
    ServerAliveInterval 60       # Send heartbeat every 60 seconds
    ServerAliveCountMax 3        # Disconnect after 3 missed responses
    ExitOnForwardFailure yes     # Disconnect if forwarding fails
    ControlMaster auto           # Reuse connection
    ControlPath ~/.ssh/cm-%r@%h:%p
    ControlPersist 600           # Keep connection for 10 minutes

# Use autossh for auto-reconnect
$ autossh -M 0 -fN tunnel
# -M 0: Disable monitoring port, rely on ServerAliveInterval

Tunnel Use Case Quick Reference

ScenarioCommandDescription
Access remote databasessh -L 3306:db:3306 jumpAccess database through bastion
NAT traversalssh -R 2222:localhost:22 publicAccess internal from public network
SOCKS proxyssh -D 1080 serverGlobal proxy
Access internal webssh -L 8080:internal-web:80 jumpAccess internal web through bastion
File transferssh -L 21:ftp:21 jumpAccess FTP through tunnel

SSH Agent

How It Works

[SSH Agent]  [SSH Client]  [Remote Server]
     
[Private key stored in Agent]

1. When SSH Client needs authentication, it requests Agent to sign
2. Agent signs with the private key but never exports it
3. Client sends the signature to the Server
4. Private key never leaves the Agent

Basic Usage

# Start Agent
$ eval $(ssh-agent)
Agent pid 12345

# Add key
$ ssh-add ~/.ssh/id_ed25519
# If the key has a passphrase, you will be prompted

# List added keys
$ ssh-add -l
256 SHA256:abc123... admin@sre.wang (ED25519)

# Remove key
$ ssh-add -d ~/.ssh/id_ed25519

# Remove all keys
$ ssh-add -D

Agent Forwarding

# Enable Agent forwarding (single session)
$ ssh -A user@jump-server
# On jump-server, you can use local keys to connect to a third server

# Configure in ~/.ssh/config
Host jump-server
    ForwardAgent yes

# Security risk: root on the bastion host can use your Agent
# In production, prefer ProxyJump over Agent forwarding

Agent Security Best Practices

# 1. Set Agent timeout
$ ssh-add -t 3600 ~/.ssh/id_ed25519  # Auto-remove after 1 hour

# 2. Confirm signature requests
$ ssh-add -c ~/.ssh/id_ed25519
# Requires confirmation each time the key is used

# 3. Limit Agent forwarding
# Do not enable ForwardAgent on untrusted servers

# 4. Use SSH Agent instead of storing private keys on servers
# Bad practice: copying private keys to every server
# Good practice: use local private keys via Agent forwarding

Audit Logging

SSH Log Locations

# Debian/Ubuntu
$ journalctl -u ssh
# Or
$ tail -f /var/log/auth.log

# RHEL/CentOS
$ journalctl -u sshd
# Or
$ tail -f /var/log/secure

Key Log Events

# Successful login
$ journalctl -u sshd | grep "Accepted"
# Accepted publickey for admin from 10.0.0.1 port 54321 ssh2: ED25519 SHA256:abc123

# Failed login
$ journalctl -u sshd | grep "Failed"
# Failed password for invalid user admin from 1.2.3.4 port 54321 ssh2

# Invalid user attempts
$ journalctl -u sshd | grep "Invalid"
# Invalid user admin from 1.2.3.4 port 54321

# Connection closed
$ journalctl -u sshd | grep "Disconnected"
# Disconnected from user admin 10.0.0.1 port 54321

# Certificate authentication
$ journalctl -u sshd | grep "Certificate"
# Certificate ID "admin_user" (serial 0) for user admin from CA accepted

Log Analysis

# Count source IPs of failed logins
$ journalctl -u sshd --since "24 hours ago" | grep "Failed" | awk '{print $NF}' | sort | uniq -c | sort -rn | head -20

# Count successfully logged-in users
$ journalctl -u sshd --since "24 hours ago" | grep "Accepted" | awk '{print $4, $6}' | sort | uniq -c | sort -rn

# Detect brute-force (multiple failures from same IP)
$ journalctl -u sshd --since "1 hour ago" | grep "Failed" | awk '{print $NF}' | sort | uniq -c | sort -rn | awk '$1 > 10 {print "ALERT:", $0}'

# View currently online users
$ who
$ w
$ last -n 20

Configuring Verbose Logging

# /etc/ssh/sshd_config
LogLevel VERBOSE
# VERBOSE level records:
# - Key fingerprints
# - Certificate IDs
# - Authentication methods
# - Port forwarding

# Audit logging
# /etc/audit/audit.rules
-w /etc/ssh/sshd_config -p wa -k ssh_config_change
-w /etc/ssh/ -p wa -k ssh_key_change

Fail2ban

How It Works

[SSH Logs] → [Fail2ban Log Parser] → [Match Failure Rules] → [Call iptables/nftables] → [Ban IP]
                                    [jail.local config]

Installation and Configuration

# Install
$ apt install fail2ban      # Debian/Ubuntu
$ dnf install fail2ban       # RHEL/CentOS

# Configuration (do not edit jail.conf directly; create jail.local)
# /etc/fail2ban/jail.d/sshd.local
[sshd]
enabled = true
port = ssh
filter = sshd
backend = systemd
maxretry = 3                  # Max failure count
findtime = 600                # Detection window (seconds)
bantime = 3600                # Ban duration (seconds)
bantime.increment = true      # Incremental banning
bantime.maxtime = 604800      # Max ban: 7 days
ignoreip = 127.0.0.1/8 10.0.0.0/8  # Whitelist
action = iptables-allports    # Ban all ports

Fail2ban Management

# Start
$ systemctl enable --now fail2ban

# View SSH jail status
$ fail2ban-client status sshd
Status for the jail: sshd
|- Filter
|  |- Currently failed: 2
|  |- Total failed:     156
|  `- File list:        /var/log/auth.log
`- Actions
   |- Currently banned: 5
   |- Total banned:     23
   `- Banned IP list:   1.2.3.4 5.6.7.8 ...

# Manually ban an IP
$ fail2ban-client set sshd banip 1.2.3.4

# Manually unban an IP
$ fail2ban-client set sshd unbanip 1.2.3.4

# View all jails
$ fail2ban-client status

Custom Rules

# /etc/fail2ban/filter.d/sshd-aggressive.conf
[INCLUDES]
before = common.conf

[Definition]
failregex = ^%(__prefix_line)sFailed publickey for invalid user <FMT_USER>.*</FMT_USER> from <HOST> port \d+ ssh2$
            ^%(__prefix_line)sInvalid user <FMT_USER>.*</FMT_USER> from <HOST> port \d+$
            ^%(__prefix_line)sConnection closed by <HOST> port \d+ \[preauth\]$

ignoreregex =

# /etc/fail2ban/jail.d/sshd.local
[sshd]
enabled = true
filter = sshd-aggressive
maxretry = 2
findtime = 300
bantime = 86400

Real-World Examples

Example 1: Production SSH Complete Configuration

# === Server Configuration ===
# /etc/ssh/sshd_config

# Protocol and encryption
Protocol 2
HostKey /etc/ssh/ssh_host_ed25519_key
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org

# Authentication
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3

# Certificate authentication
TrustedUserCAKeys /etc/ssh/user_ca.pub

# Access control
AllowGroups ssh-users admin-group
MaxStartups 10:30:100

# Timeout
ClientAliveInterval 300
ClientAliveCountMax 0  # Disconnect immediately on no response

# Logging
LogLevel VERBOSE

# Forwarding
AllowTcpForwarding yes
X11Forwarding no
AllowAgentForwarding no

# === Client Configuration ===
# ~/.ssh/config

Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3
    HashKnownHosts yes
    StrictHostKeyChecking ask

Host bastion
    HostName bastion.sre.wang
    User admin
    Port 2222
    IdentityFile ~/.ssh/id_ed25519

Host prod-*
    User deploy
    IdentityFile ~/.ssh/id_ed25519
    ProxyJump bastion
    StrictHostKeyChecking yes

Example 2: SSH Certificate Automated Issuance

#!/bin/bash
# /usr/local/bin/ssh-cert-issue.sh
# Issue short-lived SSH certificates for users

CA_KEY=~/.ssh/ca_user_key
USER_PUBKEY=$1
USERNAME=$2
VALIDITY=${3:-1d}

if [ $# -lt 2 ]; then
    echo "Usage: $0 <user_pubkey> <username> [validity]"
    exit 1
fi

# Issue certificate
ssh-keygen -s $CA_KEY \
    -I "$USERNAME@$(date +%Y%m%d)" \
    -n $USERNAME \
    -V +$VALIDITY \
    -O clear \
    -O permit-pty \
    -O force-command="/usr/local/bin/ssh-audit-wrapper.sh" \
    $USER_PUBKEY

echo "Certificate issued:"
ssh-keygen -L -f ${USER_PUBKEY%.pub}-cert.pub

Example 3: Accessing Internal Services via SSH Tunnel

# Scenario: Access internal PostgreSQL through bastion host
# Internal PostgreSQL: 192.168.1.100:5432
# Bastion: bastion.sre.wang

# Method 1: Local port forwarding
$ ssh -fNL 15432:192.168.1.100:5432 bastion
$ psql -h localhost -p 15432 -U admin -d mydb

# Method 2: Configure in ~/.ssh/config
Host db-tunnel
    HostName bastion.sre.wang
    User admin
    LocalForward 15432 192.168.1.100:5432

$ ssh -fN db-tunnel
$ psql -h localhost -p 15432 -U admin -d mydb

# Method 3: Use autossh to maintain the tunnel
$ autossh -M 0 -fN db-tunnel

# Method 4: Use ProxyCommand to connect directly (no tunnel needed)
$ psql "host=192.168.1.100 port=5432 user=admin dbname=mydb \
        sslmode=disable \
        hostaddr=127.0.0.1 \
        port=15432"

SSH Security Checklist

#!/bin/bash
# SSH security audit script

echo "=== SSH Security Check ==="

# 1. Check root login
ROOT_LOGIN=$(sshd -T | grep permitrootlogin)
echo "Root Login: $ROOT_LOGIN"

# 2. Check password authentication
PWD_AUTH=$(sshd -T | grep passwordauthentication)
echo "Password Auth: $PWD_AUTH"

# 3. Check key algorithms
echo "Host Keys:"
ls -la /etc/ssh/ssh_host_*

# 4. Check encryption configuration
echo "Ciphers:"
sshd -T | grep ciphers
echo "MACs:"
sshd -T | grep macs

# 5. Check MaxAuthTries
MAX_AUTH=$(sshd -T | grep maxauthtries)
echo "Max Auth Tries: $MAX_AUTH"

# 6. Check Fail2ban
if systemctl is-active --quiet fail2ban; then
    echo "Fail2ban: Active"
    fail2ban-client status sshd
else
    echo "Fail2ban: INACTIVE (WARNING)"
fi

# 7. Check known_hosts
echo "Known Hosts entries: $(wc -l < ~/.ssh/known_hosts 2>/dev/null || echo 0)"

# 8. Check key permissions
echo "SSH Directory Permissions:"
ls -ld ~/.ssh
ls -la ~/.ssh/authorized_keys 2>/dev/null

# 9. Check recent logins
echo "Recent Logins:"
last -n 5

# 10. Check failed logins
echo "Failed Logins (last 24h):"
journalctl -u sshd --since "24 hours ago" 2>/dev/null | grep "Failed" | wc -l

Summary

SSH is the core channel for system operations, and its security configuration directly affects the security of the entire infrastructure. Key takeaways:

  1. Key-based authentication is the foundation: Ed25519 is the preferred algorithm, password authentication must be disabled, and authorized_keys can restrict source IPs and commands.
  2. Certificate authentication is the advanced solution: Centralized key management, supports expiration and revocation — suitable for large teams and compliance requirements.
  3. sshd_config hardening is mandatory: Disable root login, restrict encryption algorithms, set timeouts and max authentication attempts.
  4. Bastion host is the standard architecture: ProxyJump is the modern approach, more secure than Agent forwarding.
  5. Port forwarding should be on-demand: Configure AllowTcpForwarding based on actual needs; X11 and Agent forwarding should be disabled.
  6. Audit logging is the security cornerstone: LogLevel VERBOSE records key fingerprints and certificate IDs; regularly analyze failed logins.
  7. Fail2ban is the standard for brute-force protection: Combined with incremental banning strategies, effectively deters automated attacks.
  8. SSH security is part of defense-in-depth: Must be combined with firewalls, intrusion detection, network segmentation, and other comprehensive protections.

The golden rule of SSH security: least privilege + layered defense. Every configuration item should follow the principle of least privilege, combined with bastion hosts, Fail2ban, and audit logging to form a multi-layered defense system.