TL;DR
Use Ed25519 for new SSH keys: ssh-keygen -t ed25519 -C "you@email.com". Always set a passphrase, use ssh-agent for convenience, and copy the public key with ssh-copy-id. Store SSH config in ~/.ssh/config for multi-server management. Disable password auth in sshd_config once keys are working. Use our online SSH key generator or visit viadreams.cc/en/tools/ssh-keygen to generate keys instantly in your browser.
SSH Key Types — RSA vs Ed25519 vs ECDSA Comparison
SSH supports several public-key algorithms. Choosing the right one matters for security, performance, and compatibility. Here is a comprehensive comparison of the three main types:
| Property | RSA-2048 | RSA-4096 | ECDSA P-256 | Ed25519 |
|---|---|---|---|---|
| Security bits | 112 | 140 | 128 | 128 (stronger curves) |
| Public key size | 381 bytes | 604 bytes | 136 bytes | 68 bytes |
| Sign speed | Moderate | Slow | Fast | Fastest |
| Side-channel resistance | Vulnerable | Vulnerable | Moderate | Excellent |
| OpenSSH support since | v1.0 (1999) | v1.0 (1999) | v5.7 (2011) | v6.5 (2014) |
| Recommendation | Legacy only | Legacy fallback | Acceptable | Preferred |
Ed25519 uses the Curve25519 elliptic curve designed by Daniel Bernstein specifically to avoid the weaknesses of NIST curves (which ECDSA uses). It is deterministic — the same message and key always produce the same signature — which eliminates the risk of nonce reuse vulnerabilities that have broken ECDSA implementations (the PlayStation 3 hack was a nonce reuse attack).
Use RSA-4096 only when you must connect to systems running OpenSSH older than 6.5 (released in 2014), some embedded devices (routers, IoT), or environments with strict FIPS-140-2 compliance requirements that mandate RSA or ECDSA.
Generating SSH Keys with ssh-keygen — All Options Explained
The ssh-keygen command is the standard tool for generating, managing, and converting SSH authentication keys. It is included with OpenSSH on macOS, Linux, and Windows 10+.
Generate an Ed25519 Key (Recommended)
# Generate Ed25519 key (recommended for new keys)
ssh-keygen -t ed25519 -C "your_email@example.com"
# Prompts:
# Enter file in which to save the key (/home/user/.ssh/id_ed25519): [Enter or custom path]
# Enter passphrase (empty for no passphrase): [use a strong passphrase!]
# Enter same passphrase again: [confirm]
# Result:
# Your identification has been saved in /home/user/.ssh/id_ed25519
# Your public key has been saved in /home/user/.ssh/id_ed25519.pub
# The key fingerprint is:
# SHA256:abc123.../your_email@example.com
# The key's randomart image is:
# +--[ED25519 256]--+
# | . o.. |
# ...
# View your public key (safe to share / copy to servers)
cat ~/.ssh/id_ed25519.pub
# ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... your_email@example.comGenerate an RSA-4096 Key (Legacy Systems)
# RSA-4096 for legacy compatibility
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
# Custom filename — useful for multiple keys
ssh-keygen -t ed25519 -C "work@company.com" -f ~/.ssh/id_ed25519_work
ssh-keygen -t ed25519 -C "personal@gmail.com" -f ~/.ssh/id_ed25519_personal
# Key with no passphrase (for automation/scripts only — less secure)
ssh-keygen -t ed25519 -C "deploy-bot@ci.example.com" -f ~/.ssh/id_ed25519_deploy -N ""
# Change passphrase on existing key
ssh-keygen -p -f ~/.ssh/id_ed25519
# Show fingerprint of existing key
ssh-keygen -l -f ~/.ssh/id_ed25519.pub
# 256 SHA256:abc123... your_email@example.com (ED25519)
# Show fingerprint in MD5 format (for GitHub key matching)
ssh-keygen -l -E md5 -f ~/.ssh/id_ed25519.pub
# 256 MD5:xx:xx:xx:xx:... your_email@example.com (ED25519)
# Convert OpenSSH private key to PEM format (for legacy tools)
ssh-keygen -p -m PEM -f ~/.ssh/id_rsaUsing ssh-agent to Manage Passphrases
# Start ssh-agent (if not already running)
eval "$(ssh-agent -s)"
# Agent pid 12345
# Add your key to the agent (enter passphrase once)
ssh-add ~/.ssh/id_ed25519
# Add with timeout (key removed from agent after 4 hours)
ssh-add -t 14400 ~/.ssh/id_ed25519
# List loaded keys
ssh-add -l
# 256 SHA256:abc123... your_email@example.com (ED25519)
# Remove a specific key from agent
ssh-add -d ~/.ssh/id_ed25519
# Remove ALL keys from agent
ssh-add -D
# macOS: add to Keychain so key persists across reboots
ssh-add --apple-use-keychain ~/.ssh/id_ed25519SSH Config File — ~/.ssh/config Host Blocks and Advanced Options
The ~/.ssh/config file is the most powerful tool for managing SSH connections to multiple servers. Instead of memorizing long ssh commands with flags, you define named profiles and connect with a short alias.
# ~/.ssh/config
# ============================================================
# GLOBAL DEFAULTS (apply to all hosts unless overridden)
# ============================================================
Host *
ServerAliveInterval 60 # Send keepalive every 60 seconds
ServerAliveCountMax 3 # Disconnect after 3 failed keepalives
AddKeysToAgent yes # Automatically add keys to ssh-agent
IdentitiesOnly yes # Only use specified identity files
HashKnownHosts yes # Hash hostnames in known_hosts for privacy
# ============================================================
# PRODUCTION SERVER
# ============================================================
Host prod
HostName 203.0.113.10
User ubuntu
Port 22
IdentityFile ~/.ssh/id_ed25519_work
ForwardAgent no # Never forward agent to prod (security risk)
# ============================================================
# STAGING SERVER (custom port)
# ============================================================
Host staging
HostName staging.example.com
User deploy
Port 2222
IdentityFile ~/.ssh/id_ed25519_work
# ============================================================
# BASTION / JUMP HOST
# ============================================================
Host bastion
HostName bastion.example.com
User admin
IdentityFile ~/.ssh/id_ed25519_work
ForwardAgent yes # Allow agent forwarding for jump
# Connect to internal server THROUGH bastion (ProxyJump)
Host internal-db
HostName 10.0.0.50
User dbadmin
IdentityFile ~/.ssh/id_ed25519_work
ProxyJump bastion # Tunnel through bastion first
# ============================================================
# MULTIPLE GITHUB ACCOUNTS
# ============================================================
Host github-personal
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_personal
Host github-work
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_work
# ============================================================
# WILDCARD FOR ALL EXAMPLE.COM SUBDOMAINS
# ============================================================
Host *.example.com
User ubuntu
IdentityFile ~/.ssh/id_ed25519_work
StrictHostKeyChecking accept-new # Auto-accept new host keys once
# Usage examples:
# ssh prod # connects to 203.0.113.10 as ubuntu
# ssh staging # connects to staging.example.com:2222
# ssh internal-db # tunnels through bastion automatically
# git clone git@github-personal:user/repo.git # uses personal keySet correct permissions on the config file — SSH will refuse to use it if permissions are too open:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/config
chmod 600 ~/.ssh/id_ed25519 # private key: owner read/write only
chmod 644 ~/.ssh/id_ed25519.pub # public key: readable by allCopy Public Key to Server — ssh-copy-id and Manual Methods
To authenticate with SSH keys, your public key must be added to ~/.ssh/authorized_keys on the remote server. There are several methods to do this.
Method 1: ssh-copy-id (Easiest)
# Copy default key to server (using password auth for the last time)
ssh-copy-id user@server.example.com
# Copy specific key
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server.example.com
# Copy to non-standard port
ssh-copy-id -i ~/.ssh/id_ed25519.pub -p 2222 user@server.example.com
# What ssh-copy-id does internally:
# 1. Reads the public key from the .pub file
# 2. SSH into the server using password auth
# 3. Creates ~/.ssh/authorized_keys if it does not exist
# 4. Appends the public key if not already present
# 5. Sets correct permissions automaticallyMethod 2: Manual (When ssh-copy-id Is Not Available)
# Option A: Pipe through ssh
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"
# Option B: Manual steps on the server
# 1. Copy the public key content (from your local machine)
cat ~/.ssh/id_ed25519.pub
# Copy the output line
# 2. On the remote server:
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... you@email.com" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
# 3. Verify correct permissions (critical — SSH won't work with wrong perms)
ls -la ~/.ssh/
# drwx------ 2 user user 4096 Jan 1 12:00 . (700)
# -rw------- 1 user user 571 Jan 1 12:00 authorized_keys (600)
# -rw------- 1 user user 387 Jan 1 12:00 id_ed25519 (600)
# -rw-r--r-- 1 user user 96 Jan 1 12:00 id_ed25519.pub (644)
# 4. Test connection (should NOT ask for password)
ssh user@server.example.com
# If it asks for password, check:
# - Permissions on ~/.ssh and authorized_keys
# - PubkeyAuthentication yes in /etc/ssh/sshd_config
# - AuthorizedKeysFile .ssh/authorized_keys in sshd_configGenerate SSH Keys Programmatically with Node.js (node-forge / ssh2)
For server-side key generation, automation scripts, or web applications that need to generate SSH keys programmatically, Node.js offers two main libraries: node-forge for RSA/PEM operations and ssh2 for SSH-specific functionality.
node-forge: Generate RSA Key Pair and Export PEM
npm install node-forge// generate-ssh-key.js
const forge = require('node-forge');
async function generateRSAKeyPair(bits = 4096, comment = 'user@host') {
return new Promise((resolve, reject) => {
console.log(`Generating RSA-${bits} key pair...`);
forge.pki.rsa.generateKeyPair({ bits, workers: -1 }, (err, keypair) => {
if (err) return reject(err);
// Export private key as PKCS#1 PEM
const privateKeyPem = forge.pki.privateKeyToPem(keypair.privateKey);
// Export private key as PKCS#8 PEM (preferred modern format)
const privateKeyPkcs8 = forge.pki.privateKeyInfoToPem(
forge.pki.wrapRsaPrivateKey(
forge.pki.privateKeyToAsn1(keypair.privateKey)
)
);
// Export public key as PEM (PKCS#8 SubjectPublicKeyInfo)
const publicKeyPem = forge.pki.publicKeyToPem(keypair.publicKey);
// Export as OpenSSH authorized_keys format
const sshPublicKey = forge.ssh.publicKeyToOpenSSH(keypair.publicKey, comment);
// Export private key as OpenSSH format
const sshPrivateKey = forge.ssh.privateKeyToOpenSSH(keypair.privateKey);
resolve({
privateKeyPem,
privateKeyPkcs8,
publicKeyPem,
sshPublicKey, // goes in ~/.ssh/authorized_keys
sshPrivateKey, // goes in ~/.ssh/id_rsa
});
});
});
}
// Encrypt private key with passphrase
function encryptPrivateKey(privateKeyPem, passphrase) {
const privateKey = forge.pki.privateKeyFromPem(privateKeyPem);
return forge.pki.encryptRsaPrivateKey(privateKey, passphrase, {
algorithm: 'aes256',
});
}
// Usage
(async () => {
const keys = await generateRSAKeyPair(4096, 'deploy@example.com');
console.log('=== OpenSSH Public Key (for authorized_keys) ===');
console.log(keys.sshPublicKey);
console.log('\n=== OpenSSH Private Key (for ~/.ssh/id_rsa) ===');
console.log(keys.sshPrivateKey);
// Save to files
const fs = require('fs');
fs.writeFileSync('./id_rsa', keys.sshPrivateKey, { mode: 0o600 });
fs.writeFileSync('./id_rsa.pub', keys.sshPublicKey, { mode: 0o644 });
console.log('\nKeys saved to ./id_rsa and ./id_rsa.pub');
})();Using Node.js Built-in crypto Module (No Dependencies)
// Node.js 15+ has built-in key generation via crypto module
const { generateKeyPair } = require('crypto');
const { promisify } = require('util');
const fs = require('fs');
const generateKeyPairAsync = promisify(generateKeyPair);
async function generateRSAKeys() {
const { publicKey, privateKey } = await generateKeyPairAsync('rsa', {
modulusLength: 4096,
publicKeyEncoding: {
type: 'spki',
format: 'pem',
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem',
cipher: 'aes-256-cbc', // Encrypt with passphrase
passphrase: 'my-passphrase',
},
});
fs.writeFileSync('private_key.pem', privateKey, { mode: 0o600 });
fs.writeFileSync('public_key.pem', publicKey, { mode: 0o644 });
console.log('RSA key pair generated successfully');
return { publicKey, privateKey };
}
// Ed25519 generation (Node.js 12+)
async function generateEd25519Keys() {
const { publicKey, privateKey } = await generateKeyPairAsync('ed25519', {
publicKeyEncoding: { type: 'spki', format: 'pem' },
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
});
return { publicKey, privateKey };
}
generateRSAKeys().then(() => console.log('Done'));SSH in Python with Paramiko — Connect, Execute Commands, SFTP
Paramiko is the most widely used Python SSH library. It implements the SSHv2 protocol directly in Python and supports key-based authentication, command execution, and SFTP file transfer.
pip install paramikoimport paramiko
import os
# ============================================================
# 1. CONNECT WITH EXISTING KEY FILE
# ============================================================
def ssh_connect_with_key(hostname, username, key_path, passphrase=None):
client = paramiko.SSHClient()
# Automatically add server's host key (use RejectPolicy in production)
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# Load private key
private_key = paramiko.Ed25519Key.from_private_key_file(
key_path,
password=passphrase # Pass passphrase if key is encrypted
)
# For RSA: paramiko.RSAKey.from_private_key_file(key_path, password=passphrase)
client.connect(
hostname=hostname,
username=username,
pkey=private_key,
timeout=10,
banner_timeout=30,
)
return client
# ============================================================
# 2. EXECUTE REMOTE COMMANDS
# ============================================================
def run_remote_command(client, command, timeout=30):
stdin, stdout, stderr = client.exec_command(command, timeout=timeout)
exit_code = stdout.channel.recv_exit_status() # Wait for completion
output = stdout.read().decode('utf-8').strip()
error = stderr.read().decode('utf-8').strip()
return exit_code, output, error
# ============================================================
# 3. SFTP FILE TRANSFER
# ============================================================
def sftp_upload(client, local_path, remote_path):
sftp = client.open_sftp()
try:
sftp.put(local_path, remote_path)
print(f"Uploaded {local_path} → {remote_path}")
finally:
sftp.close()
def sftp_download(client, remote_path, local_path):
sftp = client.open_sftp()
try:
sftp.get(remote_path, local_path)
print(f"Downloaded {remote_path} → {local_path}")
finally:
sftp.close()
# ============================================================
# 4. GENERATE RSA KEY PAIR PROGRAMMATICALLY
# ============================================================
def generate_rsa_key(bits=4096, passphrase=None):
from io import StringIO
key = paramiko.RSAKey.generate(bits=bits)
# Export private key to string
private_key_str = StringIO()
key.write_private_key(private_key_str, password=passphrase)
# Get public key in authorized_keys format
public_key_str = f"ssh-rsa {key.get_base64()} generated-by-paramiko"
return private_key_str.getvalue(), public_key_str
# ============================================================
# USAGE EXAMPLE
# ============================================================
if __name__ == "__main__":
# Connect to server
client = ssh_connect_with_key(
hostname="server.example.com",
username="ubuntu",
key_path=os.path.expanduser("~/.ssh/id_ed25519"),
passphrase=None # or "your-passphrase"
)
try:
# Run command
exit_code, output, error = run_remote_command(client, "df -h /")
print(f"Exit code: {exit_code}")
print(f"Output:\n{output}")
# Upload a file
sftp_upload(client, "/local/config.json", "/remote/config.json")
# Download a file
sftp_download(client, "/remote/log.txt", "/local/log.txt")
finally:
client.close()Git SSH Setup — GitHub, GitLab, and Bitbucket
SSH is the recommended authentication method for Git operations. Unlike HTTPS (which requires tokens or passwords), SSH keys authenticate without prompting every time you push or pull.
# ============================================================
# STEP 1: Generate key if you don't have one
# ============================================================
ssh-keygen -t ed25519 -C "your_email@example.com"
# ============================================================
# STEP 2: Start agent and add key
# ============================================================
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
# macOS: persist across reboots via Keychain
# Add to ~/.ssh/config:
# Host github.com
# AddKeysToAgent yes
# UseKeychain yes # macOS only
# IdentityFile ~/.ssh/id_ed25519
# ============================================================
# STEP 3: Copy public key to clipboard
# ============================================================
# macOS
cat ~/.ssh/id_ed25519.pub | pbcopy
# Linux (xclip)
cat ~/.ssh/id_ed25519.pub | xclip -selection clipboard
# Linux (xsel)
cat ~/.ssh/id_ed25519.pub | xsel --clipboard --input
# Windows (PowerShell)
Get-Content ~/.ssh/id_ed25519.pub | Set-Clipboard
# ============================================================
# STEP 4: Add key to Git hosting service
# ============================================================
# GitHub: Settings → SSH and GPG keys → New SSH key
# GitLab: User Settings → SSH Keys
# Bitbucket: Personal settings → SSH keys
# ============================================================
# STEP 5: Test connection
# ============================================================
ssh -T git@github.com
# Hi username! You've successfully authenticated, but GitHub
# does not provide shell access.
ssh -T git@gitlab.com
# Welcome to GitLab, @username!
ssh -T git@bitbucket.org
# logged in as username.
# ============================================================
# STEP 6: Use SSH URLs for repos
# ============================================================
# Clone with SSH
git clone git@github.com:username/repo.git
# Switch existing repo from HTTPS to SSH
git remote -v
# origin https://github.com/username/repo.git (fetch)
git remote set-url origin git@github.com:username/repo.git
git remote -v
# origin git@github.com:username/repo.git (fetch)
# ============================================================
# MULTIPLE GITHUB ACCOUNTS (work + personal)
# ============================================================
# ~/.ssh/config
# Host github-work
# HostName github.com
# User git
# IdentityFile ~/.ssh/id_ed25519_work
#
# Host github-personal
# HostName github.com
# User git
# IdentityFile ~/.ssh/id_ed25519_personal
# Clone using the alias
git clone git@github-work:company/project.git
git clone git@github-personal:myusername/side-project.gitSSH Tunneling — Local, Remote, and Dynamic Port Forwarding
SSH tunnels encrypt TCP traffic through an SSH connection. They are used to securely access internal services, bypass firewalls, and create secure proxies.
| Type | Flag | Direction | Use Case |
|---|---|---|---|
| Local | -L | Local → Remote | Access internal DB, admin panel |
| Remote | -R | Remote → Local | Expose localhost to internet |
| Dynamic | -D | SOCKS5 proxy | Browse web through SSH server |
# ============================================================
# LOCAL PORT FORWARDING: -L [local_addr:]local_port:dest_host:dest_port
# ============================================================
# Access a remote PostgreSQL database locally
# Traffic: localhost:5433 → SSH server → internal-db:5432
ssh -L 5433:internal-db.private:5432 user@bastion.example.com
# Then connect: psql -h localhost -p 5433 -U dbuser mydb
# Access a web app behind a firewall
ssh -L 8080:internal-app.private:80 user@bastion.example.com
# Then open: http://localhost:8080
# Multiple tunnels in one command
ssh -L 5433:db.private:5432 -L 8080:app.private:80 user@bastion.example.com
# ============================================================
# REMOTE PORT FORWARDING: -R [remote_addr:]remote_port:dest_host:dest_port
# ============================================================
# Expose local development server to the internet via VPS
# Traffic: vps-public:3000 → SSH connection → localhost:3000
ssh -R 3000:localhost:3000 user@vps.example.com
# Now anyone can access http://vps.example.com:3000 to see your local app
# Make accessible on all remote interfaces (requires GatewayPorts yes in sshd_config)
ssh -R 0.0.0.0:3000:localhost:3000 user@vps.example.com
# ============================================================
# DYNAMIC PORT FORWARDING (SOCKS5 PROXY): -D
# ============================================================
# Create SOCKS5 proxy on local port 1080 through SSH server
ssh -D 1080 user@ssh-server.example.com
# Configure browser proxy: SOCKS5 localhost:1080
# All browser traffic now routes through ssh-server.example.com
# ============================================================
# BACKGROUND / PERSISTENT TUNNELS
# ============================================================
# -N: do not execute remote command (tunnel only)
# -f: fork to background before command execution
# -C: enable compression (useful for slow connections)
# Start tunnel in background
ssh -N -f -L 5433:db.private:5432 user@bastion.example.com
# Persistent tunnel with autossh (auto-reconnect on failure)
# brew install autossh
autossh -M 20000 -N -f -L 5433:db.private:5432 user@bastion.example.com
# ============================================================
# SSH JUMP HOST (newer alternative to tunnels)
# ============================================================
# Connect to internal server through bastion in one command
ssh -J user@bastion.example.com user@internal.private
# With custom ports
ssh -J admin@bastion.example.com:2222 deploy@10.0.0.50Certificate-Based SSH — ssh-keygen -s, CA, authorized_principals, Expiry
Certificate-based SSH authentication replaces per-server authorized_keys management with a centralized Certificate Authority (CA). This scales to hundreds of servers and provides automatic key expiry, making it ideal for larger organizations.
Setting Up a Certificate Authority
# ============================================================
# STEP 1: Create the CA key pair (keep PRIVATE KEY very secure)
# ============================================================
ssh-keygen -t ed25519 -f /etc/ssh/ssh_ca -C "SSH Certificate Authority"
# This creates:
# /etc/ssh/ssh_ca (CA private key — guard this!)
# /etc/ssh/ssh_ca.pub (CA public key — distribute to servers)
# ============================================================
# STEP 2: Configure servers to trust the CA
# ============================================================
# On each server, add to /etc/ssh/sshd_config:
# TrustedUserCAKeys /etc/ssh/ssh_ca.pub
# systemctl restart sshd
# Distribute CA public key to all servers (Ansible/Chef/Puppet automation)
ansible all -m copy -a "src=/etc/ssh/ssh_ca.pub dest=/etc/ssh/ssh_ca.pub mode=0644"
ansible all -m lineinfile -a "path=/etc/ssh/sshd_config line='TrustedUserCAKeys /etc/ssh/ssh_ca.pub'"
ansible all -m service -a "name=sshd state=restarted"
# ============================================================
# STEP 3: Sign a user's public key to create a certificate
# ============================================================
# -s: CA private key
# -I: key identity (appears in auth logs)
# -n: authorized principals (comma-separated usernames allowed)
# -V: validity period (+52w = 52 weeks, +1d = 1 day, -5m:+1h = 5 min ago to 1 hour)
# -z: serial number (for auditing/revocation)
ssh-keygen -s /etc/ssh/ssh_ca -I "alice@example.com" -n ubuntu,admin -V +52w -z 1001 ~/.ssh/id_ed25519.pub
# Creates: ~/.ssh/id_ed25519-cert.pub
# The certificate is automatically used when the matching private key is used
# View certificate details
ssh-keygen -L -f ~/.ssh/id_ed25519-cert.pub
# Type: ssh-ed25519-cert-v01@openssh.com user certificate
# Public key: ED25519-CERT SHA256:abc...
# Signing CA: ED25519 SHA256:xyz... (ssh_ca)
# Key ID: "alice@example.com"
# Serial: 1001
# Valid: from 2024-01-01T00:00:00 to 2025-01-01T00:00:00
# Principals: ubuntu, admin
# Critical Options: (none)
# Extensions: permit-pty, permit-user-rc, permit-X11-forwarding, ...
# ============================================================
# STEP 4: Restrict with authorized_principals (optional)
# ============================================================
# Instead of authorized_keys, use authorized_principals to map
# certificates to allowed usernames per server:
# /etc/ssh/sshd_config:
# AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u
# /etc/ssh/auth_principals/ubuntu (lists principals allowed to log in as ubuntu)
# alice@example.com
# bob@example.com
# ============================================================
# CERTIFICATE REVOCATION
# ============================================================
# Revoke a specific key or certificate serial:
ssh-keygen -k -f /etc/ssh/revoked_keys -z 1001 /etc/ssh/ssh_ca.pub
# Server config: RevokedKeys /etc/ssh/revoked_keysSSH Security Best Practices — sshd_config, fail2ban, known_hosts, HSM
Hardened sshd_config
# /etc/ssh/sshd_config — Hardened configuration
# ============================================================
# DISABLE PASSWORD AUTHENTICATION (use keys only)
# ============================================================
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
UsePAM no
PermitEmptyPasswords no
# ============================================================
# DISABLE ROOT LOGIN
# ============================================================
PermitRootLogin no
# Or if you must allow root key login:
# PermitRootLogin prohibit-password
# ============================================================
# RESTRICT ALGORITHMS TO MODERN CRYPTOGRAPHY
# ============================================================
HostKeyAlgorithms ssh-ed25519,rsa-sha2-256,rsa-sha2-512
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com
# ============================================================
# REDUCE ATTACK SURFACE
# ============================================================
Port 2222 # Non-default port (reduces bot scans)
AddressFamily inet # IPv4 only (or inet6 for IPv6 only)
ListenAddress 0.0.0.0
LoginGraceTime 30 # 30 seconds to authenticate
MaxAuthTries 3 # 3 attempts before disconnect
MaxSessions 5 # Max concurrent sessions per connection
ClientAliveInterval 300 # Server-side keepalive every 5 min
ClientAliveCountMax 2 # Disconnect after 2 missed keepalives
# ============================================================
# RESTRICT WHO CAN LOG IN
# ============================================================
AllowUsers alice bob deploy # Whitelist specific users
# Or whitelist groups:
# AllowGroups sshusers admins
# ============================================================
# DISABLE UNUSED FEATURES
# ============================================================
X11Forwarding no
AllowAgentForwarding no # Disable unless you need jump hosts
AllowTcpForwarding no # Disable unless you need tunneling
GatewayPorts no
PermitTunnel no
Banner /etc/ssh/banner.txt # Display legal warning on connect
# Apply changes:
# sshd -t (test configuration before restart)
# systemctl restart sshdfail2ban Configuration for SSH
# Install fail2ban
apt install fail2ban
# /etc/fail2ban/jail.local
[DEFAULT]
bantime = 3600 # Ban for 1 hour
findtime = 600 # Count failures within 10 minutes
maxretry = 3 # Ban after 3 failures
[sshd]
enabled = true
port = 2222 # Match your SSH port
logpath = %(sshd_log)s
backend = %(sshd_backend)s
maxretry = 3
bantime = 86400 # Ban for 24 hours
# Check banned IPs
fail2ban-client status sshd
# Status for the jail: sshd
# |- Filter
# | |- Currently failed: 2
# | |- Total failed: 15
# | `- File list: /var/log/auth.log
# `- Actions
# |- Currently banned: 1
# |- Total banned: 3
# `- Banned IP list: 192.168.1.100
# Unban an IP
fail2ban-client set sshd unbanip 192.168.1.100known_hosts Management
# ~/.ssh/known_hosts stores server public keys to prevent MITM attacks
# Verify a server's host key fingerprint (first connection)
ssh -o FingerprintHash=sha256 user@server.example.com
# The authenticity of host 'server.example.com' can't be established.
# ED25519 key fingerprint is SHA256:abc123...
# Are you sure you want to continue connecting (yes/no)?
# ALWAYS verify this fingerprint out-of-band (e.g., in server console) before accepting!
# View host keys in known_hosts
cat ~/.ssh/known_hosts
# Remove a specific host (e.g., after server rebuild)
ssh-keygen -R server.example.com
ssh-keygen -R 203.0.113.10 # Also remove by IP
# Hash known_hosts for privacy (hides hostnames from local attackers)
ssh-keygen -H -f ~/.ssh/known_hosts
# Scan server host key in advance (to add before first connection)
ssh-keyscan -t ed25519 server.example.com >> ~/.ssh/known_hosts
# In CI/CD: disable strict host checking (ONLY for known/controlled targets)
# WARNING: disabling StrictHostKeyChecking defeats MITM protection
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null user@server.example.com
# Better CI/CD approach: pre-populate known_hosts with verified fingerprint
ssh-keyscan -H server.example.com >> ~/.ssh/known_hosts
ssh user@server.example.com # Now it will verify against known fingerprintHardware Security Module (HSM) and SSH Keys
# YubiKey / FIDO2 Hardware Keys for SSH (OpenSSH 8.2+)
# The private key never leaves the hardware device
# Generate a resident FIDO2 key (stored on YubiKey)
ssh-keygen -t ed25519-sk -O resident -C "hardware-key@example.com"
# -sk suffix: Security Key variant
# -O resident: store key handle on the device (survives device reset)
# Generate non-resident key (requires key handle file)
ssh-keygen -t ed25519-sk -C "yubikey@example.com"
# Retrieve resident keys from YubiKey
ssh-keygen -K
# Writes id_ed25519_sk_rk and id_ed25519_sk_rk.pub
# PIN/touch verification for each signature
ssh-keygen -t ed25519-sk -O verify-required -C "high-security@example.com"
# PKCS#11 module for HSM (for enterprise HSMs, smart cards)
ssh -I /usr/lib/opensc-pkcs11.so user@server.example.com
# In ~/.ssh/config:
# Host secure-server
# PKCS11Provider /usr/lib/opensc-pkcs11.so
# IdentityFile /path/to/certificate.pem
# HashiCorp Vault SSH Secrets Engine
# Vault acts as a CA and signs SSH certificates on demand
vault write ssh/sign/my-role public_key=@$HOME/.ssh/id_ed25519.pub
# Returns a signed certificate valid for TTL durationGenerate SSH Keys Online
Need to generate SSH keys without installing tools? Use our online SSH key generator — all cryptography runs in your browser, keys are never sent to any server.
Open SSH Key Generator →Key Takeaways
- Use Ed25519 for new keys: fastest, smallest, most secure, and resistant to side-channel attacks. Use RSA-4096 only for legacy compatibility.
- Always set a passphrase on private keys stored on disk. Use
ssh-agentto avoid entering it repeatedly. - Correct permissions are mandatory:
700on~/.ssh/,600on private keys andauthorized_keys,644on public keys. - Use ~/.ssh/config to manage multiple servers, keys, and jump hosts without memorizing long commands.
- Disable password auth in sshd_config (
PasswordAuthentication no) once SSH keys are working. Add fail2ban as a defense-in-depth measure. - For Git: use SSH URLs (
git@github.com:user/repo.git) instead of HTTPS. For multiple accounts, define per-host aliases in~/.ssh/config. - SSH tunneling with
-L(local),-R(remote), and-D(dynamic/SOCKS5) provides encrypted access to internal services. Use-N -ffor background tunnels. - Certificate-based SSH eliminates per-server authorized_keys management at scale. Certificates support automatic expiry and centralized revocation.
- FIDO2/YubiKey SSH keys (ed25519-sk) store the private key in hardware — the key never touches the filesystem and requires physical presence to sign.
- Validate host keys: always verify the fingerprint of a new server before accepting. Never use
StrictHostKeyChecking=noin production.