SSH Config: The Complete Guide for 2026
SSH (Secure Shell) is the backbone of remote server administration, secure file transfer, and encrypted communication across the internet. Every developer, sysadmin, and DevOps engineer uses SSH daily, yet most barely scratch the surface of what it can do. A well-configured ~/.ssh/config file can eliminate repetitive typing, enforce security best practices, and unlock powerful features like tunneling, multiplexing, and jump hosts.
This guide covers everything from generating your first SSH key to advanced configuration patterns used in production infrastructure.
1. What Is SSH and the SSH Config File
SSH is a cryptographic network protocol that provides encrypted communication between two machines. It replaced insecure protocols like Telnet, rlogin, and FTP for remote administration. The OpenSSH suite, included with virtually every Linux distribution and macOS, is the standard implementation.
The SSH client reads configuration from three sources, in order of priority:
- Command-line options — flags like
-p 2222or-i ~/.ssh/mykey - User config —
~/.ssh/config - System config —
/etc/ssh/ssh_config
The first matching value for each option wins. This is why you put specific Host blocks before general ones in your config.
# Check your SSH version
ssh -V
# OpenSSH_9.7p1, OpenSSL 3.3.1 4 Jun 2024
# Basic SSH connection
ssh user@hostname
# With options
ssh -p 2222 -i ~/.ssh/mykey user@hostname
2. SSH Key Generation
SSH keys use asymmetric cryptography: a private key stays on your machine, and a public key goes on every server you want to access. Key-based authentication is more secure than passwords and can be automated.
Ed25519 (Recommended)
# Generate an Ed25519 key (modern, fast, secure)
ssh-keygen -t ed25519 -C "you@example.com"
# With a custom filename
ssh-keygen -t ed25519 -C "work-laptop" -f ~/.ssh/id_ed25519_work
# The key pair is created:
# ~/.ssh/id_ed25519 (private key - NEVER share this)
# ~/.ssh/id_ed25519.pub (public key - copy to servers)
RSA (Legacy Compatibility)
# Generate a 4096-bit RSA key (use only if ed25519 is unsupported)
ssh-keygen -t rsa -b 4096 -C "you@example.com"
# AVOID: default 2048-bit RSA or older DSA keys
# ssh-keygen -t dsa # Don't use - deprecated and weak
# ssh-keygen -t rsa # 3072-bit default is okay, 4096 is better
Copying Your Public Key to a Server
# The easy way (recommended)
ssh-copy-id user@server
# With a specific key
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server
# Manual method (if ssh-copy-id is unavailable)
cat ~/.ssh/id_ed25519.pub | ssh user@server "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
File Permissions
# SSH is strict about permissions - wrong permissions = connection refused
chmod 700 ~/.ssh
chmod 600 ~/.ssh/config
chmod 600 ~/.ssh/id_ed25519 # Private keys: owner read/write only
chmod 644 ~/.ssh/id_ed25519.pub # Public keys: world-readable is fine
chmod 600 ~/.ssh/authorized_keys
3. The ~/.ssh/config File
The SSH config file eliminates repetitive command-line arguments. Instead of typing ssh -p 2222 -i ~/.ssh/work_key deploy@prod-server.company.com every time, you define a host alias and type ssh prod.
# ~/.ssh/config
# Create it if it doesn't exist:
# touch ~/.ssh/config && chmod 600 ~/.ssh/config
# Basic host entry
Host prod
HostName prod-server.company.com
User deploy
Port 2222
IdentityFile ~/.ssh/id_ed25519_work
# Now just type: ssh prod
Common Options
Host myserver
HostName 192.168.1.100 # IP address or domain name
User admin # Login username
Port 22 # SSH port (default: 22)
IdentityFile ~/.ssh/id_work # Private key to use
IdentitiesOnly yes # Only use the specified key, not all keys
ForwardAgent no # Don't forward SSH agent (default)
ServerAliveInterval 60 # Send keepalive every 60 seconds
ServerAliveCountMax 3 # Disconnect after 3 missed keepalives
Compression yes # Enable compression (good for slow links)
LogLevel INFO # Logging verbosity
4. Host Aliases and Wildcards
The Host keyword supports multiple names and wildcard patterns. This lets you apply settings to groups of servers efficiently.
# Multiple aliases for the same server
Host prod production prod-server
HostName 10.0.1.50
User deploy
# Wildcard: all servers in your staging environment
Host staging-*
User staging-deploy
IdentityFile ~/.ssh/id_ed25519_staging
Port 2222
# Specific staging servers inherit the wildcard settings
Host staging-web
HostName 10.0.2.10
Host staging-api
HostName 10.0.2.11
Host staging-db
HostName 10.0.2.12
# Match all hosts (put this LAST - applies defaults)
Host *
AddKeysToAgent yes
IdentitiesOnly yes
ServerAliveInterval 60
ServerAliveCountMax 3
HashKnownHosts yes
Remember: SSH uses first-match-wins. Put specific entries before wildcard entries and put Host * at the very end of the file.
5. ProxyJump and Bastion Servers
In production environments, internal servers are not directly accessible from the internet. You connect through a bastion host (also called a jump host) that sits at the network perimeter. ProxyJump, introduced in OpenSSH 7.3, is the modern way to handle this.
# Command-line usage
ssh -J bastion.company.com internal-server
# Chain multiple jump hosts
ssh -J bastion1,bastion2 internal-server
# In ~/.ssh/config (clean and permanent)
Host bastion
HostName bastion.company.com
User jump-user
IdentityFile ~/.ssh/id_ed25519_bastion
Host internal-*
ProxyJump bastion
User admin
Host internal-web
HostName 10.0.1.10
Host internal-db
HostName 10.0.1.20
# Now just: ssh internal-web
# SSH connects to bastion first, then tunnels to 10.0.1.10
Legacy ProxyCommand (Pre-OpenSSH 7.3)
# Older method using netcat - still works but ProxyJump is preferred
Host internal-legacy
HostName 10.0.1.10
User admin
ProxyCommand ssh -W %h:%p bastion
6. SSH Tunneling
SSH tunnels encrypt traffic for any TCP-based protocol by forwarding ports through the SSH connection. There are three types.
Local Forwarding (-L)
Forward a local port to a remote destination through the SSH server. Use this to access services on the remote network as if they were local.
# Access a remote PostgreSQL database on localhost:5433
ssh -L 5433:db-server:5432 user@bastion
# Now connect to localhost:5433 as if it were db-server:5432
# Access a remote web dashboard
ssh -L 8080:internal-dashboard:80 user@bastion
# Open http://localhost:8080 in your browser
# Run in background (no shell)
ssh -fNL 5433:db-server:5432 user@bastion
# -f = background, -N = no remote command
# In ~/.ssh/config
Host db-tunnel
HostName bastion.company.com
User tunnel-user
LocalForward 5433 db-server:5432
LocalForward 6380 redis-server:6379
Remote Forwarding (-R)
Expose a local service through the remote server. Use this to let others access something running on your machine.
# Expose local dev server (port 3000) on the remote server's port 8080
ssh -R 8080:localhost:3000 user@remote-server
# Anyone who can reach remote-server:8080 sees your local app
# Run in background
ssh -fNR 8080:localhost:3000 user@remote-server
# In ~/.ssh/config
Host expose-dev
HostName remote-server.com
User dev
RemoteForward 8080 localhost:3000
Dynamic Forwarding / SOCKS Proxy (-D)
Create a SOCKS5 proxy that routes all traffic through the SSH connection. Use this to browse the web as if you were on the remote network.
# Create a SOCKS5 proxy on localhost:1080
ssh -D 1080 user@remote-server
# Run in background
ssh -fND 1080 user@remote-server
# Configure your browser or system to use SOCKS5 proxy localhost:1080
# All HTTP/HTTPS traffic now goes through the SSH tunnel
# In ~/.ssh/config
Host socks-proxy
HostName remote-server.com
User tunnel-user
DynamicForward 1080
7. Agent Forwarding
SSH agent forwarding lets a remote server use your local SSH keys without copying private keys to the server. This is useful when you need to pull from Git or SSH to a third server from within a remote session.
# Start the SSH agent (usually already running)
eval "$(ssh-agent -s)"
# Add your key to the agent
ssh-add ~/.ssh/id_ed25519
# List loaded keys
ssh-add -l
# Connect with agent forwarding enabled
ssh -A user@server
# On the remote server, your local keys are available:
# git clone git@github.com:yourorg/repo.git # Works!
# ssh another-server # Works!
# In ~/.ssh/config (only enable for trusted servers!)
Host trusted-server
HostName trusted.company.com
User admin
ForwardAgent yes
# NEVER enable globally - a compromised server can hijack your agent
Host *
ForwardAgent no # This is the default, but be explicit
8. SSH Multiplexing (ControlMaster)
SSH multiplexing reuses a single TCP connection for multiple SSH sessions to the same host. The first connection establishes the master, and subsequent connections piggyback on it. This dramatically speeds up repeated connections and reduces authentication overhead.
# In ~/.ssh/config
Host *
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
# ControlMaster auto - automatically create a master if none exists
# ControlPath - where to store the socket file
# %r = remote user, %h = host, %p = port
# ControlPersist 600 - keep master alive for 10 minutes after last session
# Create the sockets directory
mkdir -p ~/.ssh/sockets
# First connection (establishes master - normal speed)
ssh server # Takes ~1 second for key exchange
# Second connection (reuses master - nearly instant)
ssh server # Takes ~0.05 seconds
# Check master status
ssh -O check server
# Terminate the master connection
ssh -O exit server
# Force a new master (if the socket is stale)
ssh -O stop server
9. Keep-Alive Settings
SSH connections can drop silently when a NAT gateway or firewall times out idle connections. Keep-alive packets prevent this.
# In ~/.ssh/config
Host *
# Client sends a keepalive packet every 60 seconds
ServerAliveInterval 60
# Disconnect after 3 consecutive missed responses
ServerAliveCountMax 3
# Total timeout: 60 * 3 = 180 seconds of unresponsiveness
# For flaky connections, be more aggressive
Host flaky-server
ServerAliveInterval 15
ServerAliveCountMax 5
TCPKeepAlive yes
On the server side (/etc/ssh/sshd_config), the equivalent settings are ClientAliveInterval and ClientAliveCountMax. Configure both sides for reliable connections.
10. Per-Host Identity Files
When you have multiple SSH keys (personal, work, client projects), assign specific keys to specific hosts. Without IdentitiesOnly yes, SSH tries every key in your agent, which can cause "too many authentication failures" errors.
# ~/.ssh/config
# Personal GitHub
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_personal
IdentitiesOnly yes
# Work GitHub (different account)
Host github-work
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly yes
# Client project server
Host client-prod
HostName client-server.example.com
User deployer
IdentityFile ~/.ssh/id_ed25519_client
IdentitiesOnly yes
# Usage:
# git clone git@github.com:personal/repo.git (uses personal key)
# git clone git@github-work:company/repo.git (uses work key)
# ssh client-prod (uses client key)
11. Hardening SSH
The default SSH server configuration is functional but not fully hardened. These changes significantly reduce your attack surface.
Server Configuration (/etc/ssh/sshd_config)
# Disable password authentication (key-only)
PasswordAuthentication no
ChallengeResponseAuthentication no
# Disable root login
PermitRootLogin no
# Change the default port (reduces automated scanning)
Port 2222
# Only allow specific users
AllowUsers deploy admin
# Or allow by group
AllowGroups ssh-users
# Disable X11 forwarding (if not needed)
X11Forwarding no
# Disable TCP forwarding (if not needed)
AllowTcpForwarding no
# Limit authentication attempts
MaxAuthTries 3
# Set a login grace period
LoginGraceTime 30
# Use only protocol 2
Protocol 2
# Restrict key exchange, ciphers, and MACs to strong algorithms
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org
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
# After editing sshd_config, always validate before restarting
sudo sshd -t # Test configuration syntax
sudo systemctl restart sshd # Apply changes
# IMPORTANT: Keep an existing session open when changing SSH config
# If you lock yourself out, the existing session is your safety net
Fail2Ban
# Install fail2ban to auto-ban IPs with repeated failed logins
sudo apt install fail2ban # Debian/Ubuntu
sudo dnf install fail2ban # Fedora/RHEL
# Create a local configuration
sudo tee /etc/fail2ban/jail.local <<'EOF'
[sshd]
enabled = true
port = 2222
maxretry = 3
bantime = 3600
findtime = 600
EOF
sudo systemctl enable --now fail2ban
# Check banned IPs
sudo fail2ban-client status sshd
12. SCP and Rsync over SSH
SCP (Secure Copy)
# Copy a file to a remote server
scp file.txt user@server:/remote/path/
# Copy from remote to local
scp user@server:/remote/file.txt ./local/path/
# Copy a directory recursively
scp -r ./local-dir user@server:/remote/path/
# Use SSH config aliases
scp file.txt prod:/var/www/
# With a non-standard port
scp -P 2222 file.txt user@server:/path/
Rsync (Recommended for Large Transfers)
# Rsync over SSH (default transport)
rsync -avz ./local-dir/ user@server:/remote/dir/
# Dry run first (see what would change)
rsync -avzn ./local-dir/ user@server:/remote/dir/
# With SSH config alias and custom port
rsync -avz -e "ssh -p 2222" ./src/ prod:/var/www/
# Exclude files
rsync -avz --exclude='.git' --exclude='node_modules' \
./project/ server:/deploy/
# Delete remote files not present locally (mirror)
rsync -avz --delete ./src/ server:/var/www/
# Resume interrupted transfer
rsync -avz --partial --progress large-file.tar.gz server:/tmp/
13. GitHub and GitLab SSH Key Setup
# 1. Generate a key specifically for GitHub
ssh-keygen -t ed25519 -C "you@example.com" -f ~/.ssh/id_ed25519_github
# 2. Copy the public key
cat ~/.ssh/id_ed25519_github.pub
# Copy the output, then:
# GitHub: Settings > SSH and GPG keys > New SSH key
# GitLab: Preferences > SSH Keys > Add an SSH key
# 3. Configure ~/.ssh/config
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_github
IdentitiesOnly yes
Host gitlab.com
HostName gitlab.com
User git
IdentityFile ~/.ssh/id_ed25519_gitlab
IdentitiesOnly yes
# 4. Test the connection
ssh -T git@github.com
# Hi username! You've successfully authenticated...
ssh -T git@gitlab.com
# Welcome to GitLab, @username!
Multiple GitHub Accounts
# Personal account
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_personal
IdentitiesOnly yes
# Work account (use a different Host alias)
Host github-work
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly yes
# Clone with the work account:
git clone git@github-work:company/project.git
# Set per-repo git config to use the correct email
cd project
git config user.email "you@company.com"
14. Troubleshooting SSH Connections
# Verbose output: see exactly what SSH is doing
ssh -v user@server # Basic verbosity
ssh -vv user@server # More detail
ssh -vvv user@server # Maximum detail
# Common issues to look for in verbose output:
# - "Permission denied (publickey)" = wrong key or key not accepted
# - "Connection refused" = SSH server not running or wrong port
# - "Connection timed out" = firewall blocking, wrong IP, or host down
# - "Too many authentication failures" = too many keys tried (use IdentitiesOnly)
# - "Host key verification failed" = server key changed (MITM or reinstall)
# Check which key SSH is offering
ssh -v user@server 2>&1 | grep "Offering"
# Test a specific key
ssh -i ~/.ssh/id_ed25519_work -o IdentitiesOnly=yes user@server
# Reset a known host (after legitimate server reinstall)
ssh-keygen -R hostname
ssh-keygen -R 192.168.1.100
# Check SSH server status
sudo systemctl status sshd
# Check if the SSH port is open
ss -tlnp | grep 22
nc -zv hostname 22
# Check server logs for auth failures
sudo journalctl -u sshd -f
sudo tail -f /var/log/auth.log # Debian/Ubuntu
sudo tail -f /var/log/secure # RHEL/CentOS
Common Fixes
# Fix "Permission denied" - check file permissions
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys ~/.ssh/config ~/.ssh/id_*
chmod 644 ~/.ssh/id_*.pub
# Fix "Too many authentication failures"
# Add to ~/.ssh/config for the problematic host:
Host problem-server
IdentityFile ~/.ssh/the_correct_key
IdentitiesOnly yes
# Fix stale multiplexing socket
rm ~/.ssh/sockets/*
# Or: ssh -O exit hostname
# Fix "WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED"
# Only do this if you KNOW the server was reinstalled/rebuilt:
ssh-keygen -R hostname
Complete Example: ~/.ssh/config
Here is a production-ready SSH config that ties together everything from this guide:
# ~/.ssh/config - Production SSH Configuration
# ---- Bastion / Jump Host ----
Host bastion
HostName bastion.company.com
User jump
IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly yes
# ---- Production Servers (via bastion) ----
Host prod-web
HostName 10.0.1.10
User deploy
ProxyJump bastion
Host prod-api
HostName 10.0.1.11
User deploy
ProxyJump bastion
Host prod-db
HostName 10.0.1.20
User dba
ProxyJump bastion
LocalForward 5433 localhost:5432
# ---- GitHub (personal) ----
Host github.com
User git
IdentityFile ~/.ssh/id_ed25519_personal
IdentitiesOnly yes
# ---- GitHub (work) ----
Host github-work
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly yes
# ---- Defaults (MUST be last) ----
Host *
AddKeysToAgent yes
IdentitiesOnly yes
ServerAliveInterval 60
ServerAliveCountMax 3
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
HashKnownHosts yes
ForwardAgent no
Frequently Asked Questions
Where is the SSH config file located?
The user SSH config file is at ~/.ssh/config. The system-wide client config is /etc/ssh/ssh_config, and the server config is /etc/ssh/sshd_config. If the user config does not exist, create it with touch ~/.ssh/config && chmod 600 ~/.ssh/config. SSH reads the user config first, then the system config, and the first matching value for each option wins.
What is the difference between ed25519 and RSA SSH keys?
Ed25519 uses elliptic curve cryptography with 256-bit keys, produces smaller signatures, and is faster at both signing and verification. RSA is the legacy standard — use at least 4096 bits if you must use it. Ed25519 is recommended for all new keys unless you need compatibility with OpenSSH versions before 6.5 (released in 2014). Generate one with ssh-keygen -t ed25519 -C "your@email.com".
How does SSH ProxyJump work?
ProxyJump (-J flag or ProxyJump directive) connects to a target through intermediate jump hosts. SSH establishes a connection to the jump host first, then tunnels a second SSH connection through it. You can chain multiple hosts: ssh -J bastion1,bastion2 target. In your config, add ProxyJump bastion under the target Host entry. It replaced the older ProxyCommand ssh -W %h:%p bastion approach.
What is SSH agent forwarding and is it safe?
Agent forwarding lets a remote server use your local SSH keys without copying private keys there. Enable it with ForwardAgent yes or ssh -A. The risk: anyone with root on the remote server can hijack your forwarded agent to authenticate as you. Only enable it for fully trusted servers. A safer alternative is ProxyJump, which keeps your keys entirely on your local machine.
How do I set up SSH tunneling?
SSH has three tunnel types. Local forwarding (-L 8080:remote:80) makes a remote service available on a local port. Remote forwarding (-R 8080:localhost:3000) exposes a local service on the remote server. Dynamic forwarding (-D 1080) creates a SOCKS5 proxy routing all traffic through the tunnel. All three can be set permanently in ~/.ssh/config using LocalForward, RemoteForward, and DynamicForward.
Continue Learning
- Linux Commands: The Complete Guide — master the command line that SSH connects you to
- Nginx Configuration: The Complete Guide — configure the web servers you manage over SSH
- Bash Scripting: The Complete Guide — automate tasks on the servers you SSH into