How to Set Up SSH Keys for Secure Linux Server Access
Introduction
SSH (Secure Shell) key-based authentication represents a fundamental shift in how system administrators and developers secure remote server access. Unlike traditional password authentication, which relies on memorized secrets vulnerable to brute-force attacks, SSH keys utilize asymmetric cryptography to provide virtually unbreakable authentication. This comprehensive guide explores SSH key infrastructure from basic setup through enterprise deployment scenarios, covering key generation algorithms, security hardening, certificate authorities, and automated key management at scale.
π Table of Contents
- Introduction
- 1. Understanding SSH Key Cryptography
- Public-Key Cryptography Fundamentals
- SSH Key Algorithm Comparison
- 2. Generating SSH Key Pairs
- Ed25519 Keys (Recommended)
- RSA Keys (Legacy Compatibility)
- ECDSA Keys
- Passphrase Best Practices
- Viewing Key Fingerprints
- 3. Deploying Public Keys to Servers
- Using ssh-copy-id (Simplest Method)
- Manual Deployment (No ssh-copy-id Available)
- Deploying Keys via Cloud-Init (Cloud Environments)
- Ansible Playbook for Automated Deployment
- 4. SSH Client Configuration
- ~/.ssh/config Structure
- Key Configuration Options Explained
- Multi-Hop SSH Through Bastion
- 5. SSH Server Hardening
- Comprehensive /etc/ssh/sshd_config Security
- Applying and Testing Configuration
- Fail2Ban Integration for Brute-Force Protection
- 6. SSH Agent and Key Management
- Starting and Using ssh-agent
- Persistent Agent with systemd (Linux)
- macOS Keychain Integration
- GNOME Keyring (Linux Desktop)
- 7. SSH Certificates (Enterprise Scale)
- Certificate Authority Setup
- Issuing User Certificates
- Certificate-Based Authentication
- Certificate Automation Script
- 8. Advanced Security Practices
- Hardware Security Keys (YubiKey)
- Two-Factor Authentication with SSH
- Key Rotation Procedures
- Auditing SSH Key Usage
- 9. Troubleshooting Common Issues
- Permission Problems
- Verbose Debugging
- Server-Side Diagnostics
- Common Error Messages
- 10. Enterprise Deployment Patterns
- Centralized Key Management with Vault
- AWS Systems Manager Session Manager Alternative
- Terraform Provisioner with SSH
- 11. Monitoring and Compliance
- SSH Key Inventory Script
- Compliance Scanning with Lynis
- Real-Time SSH Monitoring with Auditd
- 12. Best Practices Summary
- Key Generation
- Key Distribution
- Server Configuration
- Operational Security
- Compliance and Governance
- 13. Quick Reference Commands
- Conclusion
1. Understanding SSH Key Cryptography
Public-Key Cryptography Fundamentals
SSH keys leverage asymmetric cryptography, where a mathematically related pair of keys serves different purposes:
- Private Key: Remains confidential on your local machine, used to prove your identity
- Public Key: Distributed to servers you access, used to verify your identity
The authentication process works through a cryptographic challenge-response mechanism:
- Client initiates connection and presents public key identifier
- Server generates a random challenge encrypted with your public key
- Client decrypts challenge using private key and returns signed response
- Server verifies signature, granting access if valid
SSH Key Algorithm Comparison
Algorithm | Key Size | Security Level | Performance | Compatibility | Recommendation |
---|---|---|---|---|---|
RSA | 2048-4096 bits | Good (4096-bit) | Slower | Universal | Legacy systems only |
Ed25519 | 256 bits | Excellent | Very fast | Modern (OpenSSH 6.5+) | β Preferred |
ECDSA | 256-521 bits | Good | Fast | Good | Alternative |
DSA | 1024 bits | Deprecated | Fast | Legacy | β Avoid |
Ed25519 is the modern standard, offering superior security with smaller key sizes and faster operations compared to RSA. It’s resistant to timing attacks and doesn’t require secure random number generation for signing operations.
2. Generating SSH Key Pairs
Ed25519 Keys (Recommended)
# Generate Ed25519 key with custom comment
ssh-keygen -t ed25519 -C "john@workstation-2024" -f ~/.ssh/id_ed25519_work
# Output:
# Generating public/private ed25519 key pair.
# Enter passphrase (empty for no passphrase):
# Enter same passphrase again:
# Your identification has been saved in /home/john/.ssh/id_ed25519_work
# Your public key has been saved in /home/john/.ssh/id_ed25519_work.pub
# The key fingerprint is:
# SHA256:Xj8h3kL9mN2pQ4rT6vU8wY0zA1bC3dE5fG7hI9jK0lM john@workstation-2024
RSA Keys (Legacy Compatibility)
# Generate 4096-bit RSA key (minimum for security)
ssh-keygen -t rsa -b 4096 -C "john@legacy-systems" -f ~/.ssh/id_rsa_legacy
# Key derivation function iterations (increase resistance to brute-force)
ssh-keygen -t rsa -b 4096 -a 100 -C "john@secure" -f ~/.ssh/id_rsa_secure
ECDSA Keys
# Generate ECDSA key with 521-bit curve
ssh-keygen -t ecdsa -b 521 -C "john@cloud-servers" -f ~/.ssh/id_ecdsa_cloud
Passphrase Best Practices
A strong passphrase adds a critical security layer, protecting your private key if your workstation is compromised:
- Minimum 20 characters with mixed case, numbers, and symbols
- Use a passphrase manager or memorable sentence
- Never leave private keys unencrypted on shared systems
- Consider using ssh-agent to avoid repeated passphrase entry
Viewing Key Fingerprints
# SHA256 fingerprint (default)
ssh-keygen -lf ~/.ssh/id_ed25519.pub
# Output: 256 SHA256:Xj8h3kL9mN2pQ4rT6vU8wY0zA1bC3dE5fG7hI9jK0lM john@workstation-2024 (ED25519)
# MD5 fingerprint (legacy systems)
ssh-keygen -E md5 -lf ~/.ssh/id_ed25519.pub
# Output: 256 MD5:a1:b2:c3:d4:e5:f6:07:08:09:0a:0b:0c:0d:0e:0f:10 john@workstation-2024 (ED25519)
# Visual ASCII art (easy verification)
ssh-keygen -lv -f ~/.ssh/id_ed25519.pub
3. Deploying Public Keys to Servers
Using ssh-copy-id (Simplest Method)
# Copy default key (~/.ssh/id_ed25519.pub)
ssh-copy-id user@server.example.com
# Specify custom key file
ssh-copy-id -i ~/.ssh/id_ed25519_work.pub user@server.example.com
# Use custom port
ssh-copy-id -i ~/.ssh/id_ed25519.pub -p 2222 user@server.example.com
# Copy to multiple servers
for server in web{1..5}.example.com; do
ssh-copy-id -i ~/.ssh/id_ed25519.pub admin@$server
done
Manual Deployment (No ssh-copy-id Available)
# Method 1: Using ssh and shell redirection
cat ~/.ssh/id_ed25519.pub | ssh user@server.example.com
"mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
# Method 2: Using sftp
sftp user@server.example.com
sftp> mkdir .ssh
sftp> chmod 700 .ssh
sftp> put /home/john/.ssh/id_ed25519.pub .ssh/authorized_keys
sftp> chmod 600 .ssh/authorized_keys
sftp> quit
Deploying Keys via Cloud-Init (Cloud Environments)
#cloud-config
users:
- name: admin
ssh_authorized_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG8h3kL9mN2pQ4rT6vU8wY0zA1bC3dE5fG7hI9jK0lMn john@workstation
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC8... john@backup-key
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
Ansible Playbook for Automated Deployment
---
- name: Deploy SSH Keys to Infrastructure
hosts: all
tasks:
- name: Ensure .ssh directory exists
file:
path: "/home/{{ ansible_user }}/.ssh"
state: directory
mode: '0700'
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
- name: Deploy authorized keys
authorized_key:
user: "{{ ansible_user }}"
key: "{{ lookup('file', item) }}"
state: present
loop:
- ~/.ssh/id_ed25519.pub
- ~/.ssh/id_rsa_backup.pub
- name: Remove old deprecated keys
authorized_key:
user: "{{ ansible_user }}"
key: "{{ item }}"
state: absent
loop: "{{ deprecated_keys }}"
4. SSH Client Configuration
~/.ssh/config Structure
# Production web servers
Host web-prod-*
HostName %h.example.com
User admin
IdentityFile ~/.ssh/id_ed25519_production
Port 22
ForwardAgent no
StrictHostKeyChecking yes
UserKnownHostsFile ~/.ssh/known_hosts_production
# Development environment
Host dev-*.internal
HostName %h
User developer
IdentityFile ~/.ssh/id_ed25519_dev
Port 2222
ProxyJump bastion.example.com
ForwardAgent yes
StrictHostKeyChecking accept-new
# Bastion/Jump host
Host bastion
HostName bastion.example.com
User jumpuser
IdentityFile ~/.ssh/id_ed25519_bastion
Port 22
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h:%p
ControlPersist 10m
# GitHub
Host github.com
User git
IdentityFile ~/.ssh/id_ed25519_github
IdentitiesOnly yes
# Wildcard defaults (must be last)
Host *
AddKeysToAgent yes
UseKeychain yes # macOS only
ServerAliveInterval 60
ServerAliveCountMax 3
TCPKeepAlive yes
Compression yes
Key Configuration Options Explained
Option | Purpose | Recommended Value |
---|---|---|
IdentityFile | Specify which private key to use | ~/.ssh/id_ed25519 |
IdentitiesOnly | Only use specified identity files | yes (prevents key guessing) |
StrictHostKeyChecking | Validate server fingerprints | yes (production) |
ForwardAgent | Allow key forwarding through server | no (security risk) |
ProxyJump | Jump through bastion host | bastion.example.com |
ControlMaster | Reuse SSH connections | auto |
ServerAliveInterval | Keep connection alive (seconds) | 60 |
Multi-Hop SSH Through Bastion
# Traditional method (deprecated)
ssh -J bastion.example.com user@internal-server.local
# Modern ProxyJump configuration
Host internal-*
ProxyJump bastion.example.com
User admin
IdentityFile ~/.ssh/id_ed25519
# Multi-hop through multiple bastions
Host production-db
HostName db.prod.internal
ProxyJump bastion1.example.com,bastion2.prod.internal
User dbadmin
5. SSH Server Hardening
Comprehensive /etc/ssh/sshd_config Security
# Network and Protocol
Port 22 # Consider non-standard port (security through obscurity)
AddressFamily inet # IPv4 only (or 'any' for dual-stack)
ListenAddress 0.0.0.0
Protocol 2 # Implicit in modern OpenSSH
# Authentication Methods
PubkeyAuthentication yes
PasswordAuthentication no # Disable password login
ChallengeResponseAuthentication no
UsePAM yes
PermitRootLogin prohibit-password # Or 'no' for maximum security
# Key Management
AuthorizedKeysFile .ssh/authorized_keys
AuthorizedKeysCommand /usr/local/bin/fetch-keys.sh # Dynamic key retrieval
AuthorizedKeysCommandUser nobody
TrustedUserCAKeys /etc/ssh/ca_user_key.pub # Certificate authority
# Cryptographic Algorithms (Modern Only)
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256
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
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256
# Connection Security
MaxAuthTries 3
MaxSessions 10
ClientAliveInterval 300 # 5 minutes
ClientAliveCountMax 2
LoginGraceTime 60
TCPKeepAlive yes
# User Restrictions
AllowUsers admin deployer developer
AllowGroups ssh-users
DenyUsers root guest
PermitEmptyPasswords no
PermitUserEnvironment no
# Forwarding and Tunneling
AllowTcpForwarding no
AllowStreamLocalForwarding no
GatewayPorts no
PermitTunnel no
X11Forwarding no
# Logging
SyslogFacility AUTH
LogLevel VERBOSE # Or INFO for less verbosity
# SFTP Subsystem
Subsystem sftp /usr/lib/openssh/sftp-server -f AUTHPRIV -l INFO
# Match Blocks for Conditional Configuration
Match Group sftp-only
ForceCommand internal-sftp
ChrootDirectory /home/%u
AllowTcpForwarding no
X11Forwarding no
Match Address 10.0.0.0/8
PasswordAuthentication yes # Internal network exception
Applying and Testing Configuration
# Validate configuration syntax
sudo sshd -t
# Restart SSH service (keep existing connection open!)
sudo systemctl restart sshd
# Verify service status
sudo systemctl status sshd
# Test new connection in separate terminal
ssh -v user@localhost
Fail2Ban Integration for Brute-Force Protection
# /etc/fail2ban/jail.local
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
findtime = 600 # 10 minutes
bantime = 3600 # 1 hour ban
ignoreip = 127.0.0.1/8 10.0.0.0/8 # Whitelist
# Activate and monitor
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
sudo fail2ban-client status sshd
6. SSH Agent and Key Management
Starting and Using ssh-agent
# Start agent in current shell
eval "$(ssh-agent -s)"
# Output: Agent pid 12345
# Add key to agent
ssh-add ~/.ssh/id_ed25519
# Enter passphrase:
# Identity added: /home/john/.ssh/id_ed25519 (john@workstation)
# Add key with lifetime (1 hour)
ssh-add -t 3600 ~/.ssh/id_ed25519_temp
# List loaded keys
ssh-add -l
# Output: 256 SHA256:Xj8h3kL9mN2pQ4rT6vU8wY0zA1bC3dE5fG7hI9jK0lM john@workstation (ED25519)
# Remove specific key
ssh-add -d ~/.ssh/id_ed25519_temp
# Remove all keys
ssh-add -D
Persistent Agent with systemd (Linux)
# ~/.config/systemd/user/ssh-agent.service
[Unit]
Description=SSH Agent
Documentation=man:ssh-agent(1)
[Service]
Type=simple
Environment=SSH_AUTH_SOCK=%t/ssh-agent.socket
ExecStart=/usr/bin/ssh-agent -D -a $SSH_AUTH_SOCK
ExecStartPost=/usr/bin/systemctl --user set-environment SSH_AUTH_SOCK=${SSH_AUTH_SOCK}
[Install]
WantedBy=default.target
# Activate service
systemctl --user enable ssh-agent
systemctl --user start ssh-agent
# Add to ~/.bashrc or ~/.zshrc
export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/ssh-agent.socket"
macOS Keychain Integration
# Add to ~/.ssh/config
Host *
AddKeysToAgent yes
UseKeychain yes
IdentityFile ~/.ssh/id_ed25519
# Add key to keychain (one-time passphrase entry)
ssh-add --apple-use-keychain ~/.ssh/id_ed25519
GNOME Keyring (Linux Desktop)
# Add to ~/.bashrc
if [ -n "$DESKTOP_SESSION" ]; then
eval $(gnome-keyring-daemon --start --components=ssh)
export SSH_AUTH_SOCK
fi
7. SSH Certificates (Enterprise Scale)
Certificate Authority Setup
SSH certificates solve key management at scale by allowing centralized trust instead of distributing individual public keys.
# Generate CA key pair (secure this key!)
ssh-keygen -t ed25519 -f /etc/ssh/ca_user_key -C "User Certificate Authority"
# Restrict CA key permissions
chmod 600 /etc/ssh/ca_user_key
chmod 644 /etc/ssh/ca_user_key.pub
# Configure server to trust CA (/etc/ssh/sshd_config)
TrustedUserCAKeys /etc/ssh/ca_user_key.pub
Issuing User Certificates
# Sign user's public key with CA
ssh-keygen -s /etc/ssh/ca_user_key
-I john.doe
-n admin,developer
-V +52w
-z 1001
~/.ssh/id_ed25519.pub
# Output: Signed user key /home/john/.ssh/id_ed25519-cert.pub
# Certificate details
ssh-keygen -L -f ~/.ssh/id_ed25519-cert.pub
# Output:
# Type: ssh-ed25519-cert-v01@openssh.com user certificate
# Public key: ED25519-CERT SHA256:Xj8h3kL9mN2pQ4rT6vU8wY0zA1bC3dE5fG7hI9jK0lM
# Signing CA: ED25519 SHA256:aB1cD2eF3gH4iJ5kL6mN7oP8qR9sT0uV1wX2yZ3
# Key ID: "john.doe"
# Serial: 1001
# Valid: from 2024-01-15T10:00:00 to 2025-01-14T10:00:00
# Principals:
# admin
# developer
# Critical Options: (none)
# Extensions:
# permit-X11-forwarding
# permit-agent-forwarding
# permit-port-forwarding
# permit-pty
# permit-user-rc
Certificate-Based Authentication
# Client automatically uses certificate if present
ssh admin@server.example.com
# Server logs show certificate authentication
# Jan 15 10:05:23 server sshd[12345]: Accepted publickey for admin from 192.0.2.10 port 54321 ssh2: ED25519-CERT ID john.doe (serial 1001) CA ED25519 SHA256:aB1cD2eF3gH4iJ5kL6mN7oP8qR9sT0uV1wX2yZ3
Certificate Automation Script
#!/bin/bash
# /usr/local/bin/issue-ssh-cert.sh
set -euo pipefail
USER_ID="$1"
PRINCIPALS="$2"
VALIDITY="${3:-+4w}" # Default 4 weeks
CA_KEY="/secure/ca_user_key"
SERIAL_FILE="/var/lib/ssh-ca/serial"
# Generate unique serial
SERIAL=$(cat "$SERIAL_FILE")
echo $((SERIAL + 1)) > "$SERIAL_FILE"
# Sign certificate
ssh-keygen -s "$CA_KEY"
-I "$USER_ID"
-n "$PRINCIPALS"
-V "$VALIDITY"
-z "$SERIAL"
-O clear
-O permit-pty
-O permit-port-forwarding
"keys/${USER_ID}_ed25519.pub"
echo "Certificate issued: ${USER_ID}_ed25519-cert.pub (Serial: $SERIAL)"
8. Advanced Security Practices
Hardware Security Keys (YubiKey)
# Generate resident key on YubiKey
ssh-keygen -t ed25519-sk -O resident -O verify-required -C "john@yubikey"
# List resident keys
ssh-keygen -K
# Authentication requires physical touch
ssh user@server.example.com
# [Touch YubiKey to authenticate]
Two-Factor Authentication with SSH
# Install Google Authenticator PAM module
sudo apt install libpam-google-authenticator
# Configure PAM (/etc/pam.d/sshd)
auth required pam_google_authenticator.so nullok
# Update sshd_config
ChallengeResponseAuthentication yes
AuthenticationMethods publickey,keyboard-interactive
# User setup
google-authenticator
# Scan QR code with authenticator app
Key Rotation Procedures
#!/bin/bash
# Automated key rotation
OLD_KEY="$HOME/.ssh/id_ed25519_old"
NEW_KEY="$HOME/.ssh/id_ed25519"
SERVERS=(web1 web2 db1 db2)
# Generate new key
ssh-keygen -t ed25519 -f "$NEW_KEY" -C "$(whoami)@$(hostname)-$(date +%Y%m%d)"
# Deploy new key alongside old
for server in "${SERVERS[@]}"; do
ssh-copy-id -i "${NEW_KEY}.pub" "$server"
done
# Test new key on all servers
for server in "${SERVERS[@]}"; do
ssh -i "$NEW_KEY" -o PasswordAuthentication=no "$server" "echo 'Connection successful'"
done
# Remove old key from servers
for server in "${SERVERS[@]}"; do
ssh "$server" "sed -i '/$(ssh-keygen -lf "$OLD_KEY.pub" | awk '{print $2}')/d' ~/.ssh/authorized_keys"
done
Auditing SSH Key Usage
# Server-side: Log key fingerprints (/etc/ssh/sshd_config)
LogLevel VERBOSE
# Monitor authentication logs
sudo tail -f /var/log/auth.log | grep 'Accepted publickey'
# Output: Jan 15 10:05:23 server sshd[12345]: Accepted publickey for admin from 192.0.2.10 port 54321 ssh2: ED25519 SHA256:Xj8h3kL9mN2pQ4rT6vU8wY0zA1bC3dE5fG7hI9jK0lM
# Identify users by key fingerprint
awk '/^ssh-/ {print $NF, $1, $2}' ~/.ssh/authorized_keys | while read comment type key; do
fingerprint=$(echo "$type $key" | ssh-keygen -lf -)
echo "$comment: $fingerprint"
done
9. Troubleshooting Common Issues
Permission Problems
# Correct permissions for SSH directories and files
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
chmod 600 ~/.ssh/authorized_keys
chmod 644 ~/.ssh/known_hosts
chmod 600 ~/.ssh/config
# Server-side home directory must not be writable by others
chmod 755 ~
Verbose Debugging
# Client-side debugging (increasing verbosity)
ssh -v user@server.example.com # Basic
ssh -vv user@server.example.com # More detail
ssh -vvv user@server.example.com # Maximum detail
# Example output analysis
# debug1: Offering public key: /home/john/.ssh/id_ed25519 ED25519 SHA256:Xj8h3kL9mN2pQ4rT6vU8wY0zA1bC3dE5fG7hI9jK0lM
# debug1: Server accepts key: /home/john/.ssh/id_ed25519 ED25519 SHA256:Xj8h3kL9mN2pQ4rT6vU8wY0zA1bC3dE5fG7hI9jK0lM
# debug1: Authentication succeeded (publickey)
Server-Side Diagnostics
# Check sshd configuration
sudo sshd -T | grep -E 'pubkey|authorized'
# Real-time sshd logging
sudo tail -f /var/log/auth.log
# Test sshd configuration parsing
sudo /usr/sbin/sshd -t -f /etc/ssh/sshd_config
# Check SELinux context (Red Hat/CentOS)
restorecon -R -v ~/.ssh
Common Error Messages
Error | Cause | Solution |
---|---|---|
Permission denied (publickey) | Wrong key or server config issue | Check authorized_keys, verify key fingerprint |
Too many authentication failures | ssh-agent offering wrong keys | Use IdentitiesOnly yes in config |
Host key verification failed | Server key changed or MITM attack | Verify fingerprint, remove old key from known_hosts |
Connection refused | sshd not running or firewall blocking | Check service status, firewall rules |
Connection timed out | Network issue or wrong IP/port | Verify network connectivity, check port |
10. Enterprise Deployment Patterns
Centralized Key Management with Vault
# HashiCorp Vault SSH secrets engine configuration
vault secrets enable ssh
vault write ssh/roles/admin
key_type=ca
ttl=8h
max_ttl=24h
allowed_users="admin,deployer"
default_user=admin
allowed_extensions="permit-pty,permit-port-forwarding"
# Client requests signed certificate
vault write -field=signed_key ssh/sign/admin
public_key=@~/.ssh/id_ed25519.pub > ~/.ssh/id_ed25519-cert.pub
# Server configuration (/etc/ssh/sshd_config)
TrustedUserCAKeys /etc/ssh/vault-ca.pub
AWS Systems Manager Session Manager Alternative
# IAM policy for SSM access
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ssm:StartSession"
],
"Resource": [
"arn:aws:ec2:*:*:instance/*"
],
"Condition": {
"StringEquals": {
"aws:RequestedRegion": "us-east-1"
}
}
}
]
}
# Start session (no SSH keys needed)
aws ssm start-session --target i-0123456789abcdef0
# SSH proxy through SSM
# ~/.ssh/config
Host i-*
ProxyCommand sh -c "aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'"
User ec2-user
Terraform Provisioner with SSH
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
key_name = aws_key_pair.deployer.key_name
provisioner "remote-exec" {
inline = [
"sudo apt update",
"sudo apt install -y nginx",
]
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/id_ed25519")
host = self.public_ip
}
}
}
resource "aws_key_pair" "deployer" {
key_name = "deployer-key"
public_key = file("~/.ssh/id_ed25519.pub")
}
11. Monitoring and Compliance
SSH Key Inventory Script
#!/bin/bash
# Audit SSH keys across infrastructure
SERVERS=(web1 web2 db1 db2)
OUTPUT="ssh_key_inventory_$(date +%Y%m%d).csv"
echo "Server,User,Key Type,Fingerprint,Comment" > "$OUTPUT"
for server in "${SERVERS[@]}"; do
ssh "$server" 'getent passwd | cut -d: -f1,6' | while IFS=: read user home; do
if [ -f "$home/.ssh/authorized_keys" ]; then
while read keyline; do
if [[ "$keyline" =~ ^ssh- ]]; then
type=$(echo "$keyline" | awk '{print $1}')
fingerprint=$(echo "$keyline" | ssh-keygen -lf - | awk '{print $2}')
comment=$(echo "$keyline" | awk '{print $NF}')
echo "$server,$user,$type,$fingerprint,$comment" >> "$OUTPUT"
fi
done < "$home/.ssh/authorized_keys"
fi
done
done
echo "Inventory saved to $OUTPUT"
Compliance Scanning with Lynis
# Install Lynis
sudo apt install lynis
# Run SSH security audit
sudo lynis audit system --tests-from-group SSH
# Key findings
# - Weak SSH algorithm support
# - PermitRootLogin enabled
# - PasswordAuthentication enabled
# - Missing MaxAuthTries configuration
Real-Time SSH Monitoring with Auditd
# /etc/audit/rules.d/ssh.rules
-w /etc/ssh/sshd_config -p wa -k sshd_config_changes
-w /home -p wa -k ssh_key_changes
-a always,exit -F arch=b64 -S execve -F path=/usr/sbin/sshd -k sshd_execution
# Reload rules
sudo augenrules --load
# Search audit logs
sudo ausearch -k ssh_key_changes -ts recent
12. Best Practices Summary
Key Generation
- Use Ed25519 for new keys (modern, secure, fast)
- Generate 4096-bit RSA only for legacy compatibility
- Always encrypt private keys with strong passphrases
- Use descriptive comments for key identification
- Store private keys securely (encrypted filesystems, hardware tokens)
Key Distribution
- Use ssh-copy-id or configuration management tools
- Never send private keys over email or insecure channels
- Implement key rotation every 90-180 days
- Maintain key inventory for compliance
- Use certificate authorities for enterprise scale
Server Configuration
- Disable password authentication completely
- Restrict root login (PermitRootLogin no or prohibit-password)
- Use fail2ban or similar for brute-force protection
- Enable verbose logging for security audits
- Configure modern cryptographic algorithms only
- Implement user and group access controls
Operational Security
- Use ssh-agent with timeout for passphrase caching
- Configure connection multiplexing to reduce authentication overhead
- Implement bastion hosts for production access
- Never use agent forwarding to untrusted hosts
- Monitor SSH logs for unusual activity
- Regularly audit authorized_keys files
Compliance and Governance
- Document key management procedures
- Maintain access logs for 90+ days
- Implement least-privilege access controls
- Use hardware security keys for privileged accounts
- Conduct quarterly key rotation audits
- Integrate SSH authentication with identity providers (LDAP, SSO)
13. Quick Reference Commands
# Generate Ed25519 key
ssh-keygen -t ed25519 -C "user@host"
# Copy key to server
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server
# Add key to agent
ssh-add ~/.ssh/id_ed25519
# List agent keys
ssh-add -l
# Test connection with debugging
ssh -vvv user@server
# Check server key fingerprint
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
# Verify client configuration
ssh -G user@server
# Generate key fingerprint
ssh-keygen -lf ~/.ssh/id_ed25519.pub
# Remove host from known_hosts
ssh-keygen -R server.example.com
# Forward local port through SSH
ssh -L 8080:localhost:80 user@server
# SOCKS proxy through SSH
ssh -D 1080 user@server
# Copy files securely
scp -i ~/.ssh/id_ed25519 file.txt user@server:/path/
# Sync directories
rsync -avz -e "ssh -i ~/.ssh/id_ed25519" /local/ user@server:/remote/
Conclusion
SSH key-based authentication forms the security foundation for modern infrastructure management. This guide covered everything from basic key generation through enterprise certificate authorities and compliance monitoring. By implementing these practicesβusing Ed25519 keys, disabling password authentication, leveraging certificate authorities for scale, and maintaining comprehensive audit trailsβyou establish defense-in-depth for your Linux servers. Regular key rotation, hardware security token integration, and centralized key management with tools like HashiCorp Vault further strengthen your security posture against evolving threats.
Remember that SSH security is not a one-time configuration but an ongoing operational practice. Schedule quarterly security audits, maintain key inventories, monitor authentication logs, and stay current with OpenSSH security advisories. The investment in robust SSH key infrastructure pays dividends in reduced breach risk and simplified compliance with security frameworks like SOC 2, PCI DSS, and NIST 800-53.
Was this article helpful?
About Ramesh Sundararamaiah
Red Hat Certified Architect
Expert in Linux system administration, DevOps automation, and cloud infrastructure. Specializing in Red Hat Enterprise Linux, CentOS, Ubuntu, Docker, Ansible, and enterprise IT solutions.