SSH Configuration and Key Management¶
SSH is the backbone of remote Linux administration. Every server you manage, every Git push over SSH, and every secure tunnel between machines flows through OpenSSH. The basics are simple - ssh user@host and you're in - but SSH's configuration system is powerful enough to handle complex multi-hop environments, automated deployments, and security hardening that goes well beyond password authentication.
This guide assumes you're comfortable with basic SSH connections. The Networking guide covers the fundamentals if you need them.
OpenSSH Components¶
OpenSSH has two sides:
| Component | Binary | Purpose |
|---|---|---|
| Client | ssh |
Connects to remote machines |
| Server | sshd |
Accepts incoming connections |
| Key generator | ssh-keygen |
Creates and manages keys |
| Agent | ssh-agent |
Caches decrypted private keys |
| Copy ID | ssh-copy-id |
Deploys public keys to servers |
The client reads ~/.ssh/config for per-user settings. The server reads /etc/ssh/sshd_config for system-wide settings.
Key Types and Generation¶
Not all SSH key algorithms are equal. The choice affects security, compatibility, and performance.
| Algorithm | Key Size | Security | Speed | Recommendation |
|---|---|---|---|---|
| Ed25519 | 256-bit (fixed) | Excellent | Fastest | Default choice for new keys |
| ECDSA | 256/384/521-bit | Good | Fast | Acceptable, but Ed25519 preferred |
| RSA | 2048-4096 bit | Good (at 3072+) | Slower | Use only when Ed25519 isn't supported |
| DSA | 1024-bit (fixed) | Broken | - | Never use. Disabled in modern OpenSSH |
Generating Keys¶
# Ed25519 (recommended)
ssh-keygen -t ed25519 -C "ryan@workstation"
# RSA with 4096 bits (for legacy compatibility)
ssh-keygen -t rsa -b 4096 -C "ryan@workstation"
# Generate a key with a specific filename
ssh-keygen -t ed25519 -f ~/.ssh/deploy_key -C "deploy@project"
# Generate a key with no passphrase (for automation only)
ssh-keygen -t ed25519 -f ~/.ssh/automation_key -N "" -C "automation"
The -C flag adds a comment (usually your email or purpose) to help identify the key. It's stored in the public key file and visible in authorized_keys on remote servers.
Passphrases matter
A passphrase encrypts your private key at rest. Without one, anyone who copies your ~/.ssh/id_ed25519 file has full access to every server that trusts it. Always use a passphrase for interactive keys. For automation keys, use restrictive file permissions and authorized_keys options instead.
File Permissions¶
SSH refuses to use keys with loose permissions. The required settings:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519 # private key
chmod 644 ~/.ssh/id_ed25519.pub # public key
chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/config
If you see "Permissions are too open" errors, file permissions are the first thing to check.
ssh-agent¶
Typing your passphrase every time you SSH somewhere gets old fast. ssh-agent solves this by holding your decrypted private keys in memory for the duration of your session.
Starting the Agent¶
Most desktop environments start ssh-agent automatically. To start it manually:
# Start the agent and set environment variables
eval "$(ssh-agent -s)"
# Add your default key (prompts for passphrase once)
ssh-add
# Add a specific key
ssh-add ~/.ssh/deploy_key
# Add a key with a lifetime (auto-removed after timeout)
ssh-add -t 3600 ~/.ssh/id_ed25519 # 1 hour
# List loaded keys
ssh-add -l
# Remove all keys from the agent
ssh-add -D
The -t flag is useful for security-sensitive keys - the key is available for the specified time, then automatically unloaded.
Agent Forwarding¶
Agent forwarding lets you use your local SSH keys on a remote server without copying your private key there. When you SSH from server A to server B, the authentication request tunnels back to your local agent.
# Forward your agent to a remote host
ssh -A user@jumphost
# Or configure it in ~/.ssh/config
Host jumphost
ForwardAgent yes
Agent forwarding risks
When you forward your agent, anyone with root access on the remote server can use your agent socket to authenticate as you to other servers. Only forward your agent to machines you fully trust, and prefer ProxyJump over agent forwarding when possible - it keeps the private key negotiation on your local machine.
SSH_AUTH_SOCK¶
The agent communicates through a Unix socket. The SSH_AUTH_SOCK environment variable points to it:
If ssh-add says "Could not open a connection to your authentication agent", the variable is probably not set. Re-running eval "$(ssh-agent -s)" fixes it.
~/.ssh/config Deep Dive¶
The SSH client config file is the most underused productivity tool in a sysadmin's arsenal. Everything you repeat on the command line can be encoded here.
Host Patterns and Wildcards¶
# Exact match
Host webserver
HostName 10.0.1.50
User deploy
# Wildcard match
Host *.prod
User deploy
IdentityFile ~/.ssh/prod_key
StrictHostKeyChecking yes
# Match all hosts (defaults)
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
AddKeysToAgent yes
IdentitiesOnly yes
SSH processes config blocks in order. The first matching value for each directive wins. Put specific hosts before wildcards, and Host * defaults at the end.
ProxyJump (Jump Hosts)¶
Access machines behind firewalls by bouncing through a bastion host:
# Simple jump
Host internal-db
HostName 10.0.0.50
User admin
ProxyJump bastion
Host bastion
HostName bastion.example.com
User jump
# Multi-hop chain
Host deep-internal
HostName 192.168.1.100
ProxyJump bastion,middleware
ProxyJump is superior to agent forwarding for reaching internal hosts. Your private key never leaves your machine - each hop is negotiated locally through a forwarded TCP connection.
Connection Multiplexing (ControlMaster)¶
Opening multiple SSH sessions to the same host? Multiplexing reuses a single TCP connection, eliminating the handshake overhead for subsequent connections:
| Directive | Meaning |
|---|---|
ControlMaster auto |
First connection becomes the master; subsequent ones multiplex |
ControlPath |
Socket file location (%r = remote user, %h = host, %p = port) |
ControlPersist 600 |
Keep the master connection alive 10 minutes after last session disconnects |
With this config, your second ssh to the same host connects instantly - no authentication, no handshake. scp and rsync over SSH also benefit.
# Check the status of a multiplexed connection
ssh -O check webserver
# Manually close a master connection
ssh -O exit webserver
Match Blocks¶
Match blocks apply settings conditionally based on criteria beyond hostname:
# Use a specific key when connecting from the office network
Match host *.internal exec "ip route get 10.0.0.1 | grep -q 'src 10.'"
IdentityFile ~/.ssh/office_key
# Different settings for non-interactive commands
Match host * exec "test -z '$SSH_TTY'"
RequestTTY no
LogLevel ERROR
authorized_keys Options¶
The ~/.ssh/authorized_keys file on a server doesn't just list public keys - each key entry can have options that restrict what that key is allowed to do.
# Basic entry
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5... ryan@workstation
# Restricted entry: only allow from specific IPs
from="10.0.1.0/24,192.168.1.50" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5... deploy@ci
# Force a specific command (ignores whatever the client requests)
command="/opt/scripts/backup.sh",no-port-forwarding,no-X11-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5... backup@scheduler
# Maximum restriction: only the forced command, from specific hosts
restrict,command="/usr/bin/rsync --server --sender .",from="10.0.1.0/24" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5... sync@mirror
| Option | Effect |
|---|---|
command="..." |
Forces a specific command regardless of client request |
from="..." |
Restricts source IP addresses (comma-separated, supports CIDR) |
no-port-forwarding |
Disables TCP port forwarding |
no-X11-forwarding |
Disables X11 forwarding |
no-agent-forwarding |
Disables agent forwarding |
no-pty |
Disables terminal allocation |
restrict |
Enables all restrictions at once (OpenSSH 7.2+) |
environment="KEY=VAL" |
Sets environment variables for the session |
The restrict keyword is the modern approach - it disables everything, then you selectively re-enable what's needed. This is safer than listing individual no-* options because new features are restricted by default.
SSH Certificates¶
Managing authorized_keys across hundreds of servers is painful. SSH certificates solve this by introducing a Certificate Authority (CA) that signs keys. Servers trust the CA, and any key signed by it is automatically accepted.
Setting Up a User CA¶
# Generate the CA key pair (guard this private key carefully)
ssh-keygen -t ed25519 -f /etc/ssh/user_ca -C "user-ca@example.com"
# Sign a user's public key
ssh-keygen -s /etc/ssh/user_ca -I "jdoe-workstation" -n jdoe,deploy -V +52w ~/.ssh/id_ed25519.pub
| Flag | Meaning |
|---|---|
-s |
Sign with this CA key |
-I |
Key identifier (for logging) |
-n |
Principals (usernames) the certificate is valid for |
-V |
Validity period (+52w = 52 weeks from now) |
This creates ~/.ssh/id_ed25519-cert.pub. The SSH client automatically uses it alongside the private key.
Configuring the Server¶
On each server, add to /etc/ssh/sshd_config:
Now any user with a certificate signed by that CA can log in as the principals listed in their certificate - no authorized_keys entry needed.
Host Certificates¶
The reverse problem: how do users verify they're connecting to the real server? Host certificates replace the "Are you sure you want to continue connecting?" prompt.
# Sign the host's public key with a host CA
ssh-keygen -s /etc/ssh/host_ca -I "webserver.example.com" -h -V +52w /etc/ssh/ssh_host_ed25519_key.pub
# On the server, add to sshd_config:
# HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub
# On clients, add to ~/.ssh/known_hosts:
# @cert-authority *.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5... host-ca
With host certificates configured, clients automatically trust any server in *.example.com whose host key is signed by the CA. No more manually accepting fingerprints.
sshd_config Hardening¶
The SSH server configuration at /etc/ssh/sshd_config controls who can connect and how. A hardened configuration for a production server:
# Authentication
PermitRootLogin no
PasswordAuthentication no
PermitEmptyPasswords no
PubkeyAuthentication yes
AuthenticationMethods publickey
MaxAuthTries 3
LoginGraceTime 30
# Access control
AllowGroups ssh-users
# Or restrict to specific users:
# AllowUsers deploy admin jdoe
# Session
ClientAliveInterval 300
ClientAliveCountMax 2
MaxSessions 10
X11Forwarding no
# Security
Protocol 2
PermitUserEnvironment no
Banner /etc/ssh/banner
After editing, validate the config and restart:
# Check for syntax errors without restarting
sudo sshd -T
# Restart sshd to apply changes
sudo systemctl restart sshd
Test before disconnecting
Always keep your current SSH session open while testing sshd_config changes. Open a second terminal and try to connect. If the new config locks you out, your existing session is your lifeline to fix it.
| Directive | Recommended | Why |
|---|---|---|
PermitRootLogin |
no |
Force users to authenticate as themselves, then sudo |
PasswordAuthentication |
no |
Eliminates brute-force attacks entirely |
MaxAuthTries |
3 |
Limits authentication attempts per connection |
AllowGroups |
ssh-users |
Only members of this group can SSH in |
ClientAliveInterval |
300 |
Detects dead connections (300s = 5 min) |
X11Forwarding |
no |
Disable unless GUI forwarding is needed |
Port Forwarding¶
SSH tunnels encrypt traffic between two endpoints, letting you access services securely across untrusted networks.
Local Forwarding (-L)¶
Forward a local port to a remote destination through the SSH connection:
# Access a remote database on localhost:5432
ssh -L 5432:localhost:5432 user@dbserver
# Access a service on a machine behind the SSH server
ssh -L 8080:internal-app:80 user@bastion
# General form: -L local_port:destination:dest_port
After running the first command, connecting to localhost:5432 on your machine reaches the PostgreSQL server on dbserver.
Remote Forwarding (-R)¶
Expose a local service to the remote network:
# Let the remote server access your local web server
ssh -R 8080:localhost:3000 user@remote
# Anyone on the remote network can reach your localhost:3000 via remote:8080
Dynamic Forwarding (-D)¶
Create a SOCKS proxy through the SSH connection:
# Start a SOCKS5 proxy on local port 1080
ssh -D 1080 user@remote
# Configure your browser or tools to use localhost:1080 as a SOCKS5 proxy
All traffic through the proxy is routed through the SSH server. Useful for accessing internal websites from outside the network.
Config File Forwarding¶
Host tunnel-db
HostName bastion.example.com
User admin
LocalForward 5432 db.internal:5432
LocalForward 6379 redis.internal:6379
Host socks-proxy
HostName remote.example.com
User admin
DynamicForward 1080
Now ssh tunnel-db automatically sets up both port forwards.
Troubleshooting¶
Verbose Output¶
The -v flag is your primary debugging tool. Use up to three for increasing detail:
ssh -v user@host # Basic connection debugging
ssh -vv user@host # More detail
ssh -vvv user@host # Maximum verbosity
Look for lines like:
- debug1: Offering public key - which keys the client is trying
- debug1: Authentication succeeded - what method worked
- debug1: Connection refused - network-level rejection
Common Issues¶
| Problem | Cause | Fix |
|---|---|---|
| "Permission denied (publickey)" | Key not accepted | Check authorized_keys perms, ssh -vvv to see which keys are offered |
| "Permissions are too open" | Private key file is world-readable | chmod 600 ~/.ssh/id_ed25519 |
| "Host key verification failed" | Server's host key changed | Verify the change is legitimate, then ssh-keygen -R hostname |
| "Connection refused" | sshd not running or firewall | systemctl status sshd, check firewall rules |
| "Connection timed out" | Network issue or wrong port | Check connectivity, try ssh -p PORT |
Server-side Debugging¶
# Show effective sshd configuration
sudo sshd -T
# Show config for a specific connection
sudo sshd -T -C user=jdoe,host=10.0.1.50
# Check auth logs
journalctl -u sshd --since "10 minutes ago"
# On Debian/Ubuntu, auth logs also go to:
tail -f /var/log/auth.log
Further Reading¶
- OpenSSH Manual Pages - official documentation for all OpenSSH tools
- ssh_config(5) man page - client configuration reference
- sshd_config(5) man page - server configuration reference
- SSH Certificates (Smallstep guide) - practical guide to SSH certificate setup
- Mozilla SSH Guidelines - production-grade SSH hardening recommendations
- Arch Wiki: OpenSSH - comprehensive practical reference with examples
Previous: User and Group Management | Next: Log Management | Back to Index