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:

  1. Command-line options — flags like -p 2222 or -i ~/.ssh/mykey
  2. User config~/.ssh/config
  3. 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
Security warning: Anyone with root access on the remote server can use your forwarded agent to authenticate as you. Prefer ProxyJump over agent forwarding when possible — it keeps your keys entirely on your local machine.

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

⚙ Essential tools: Encode data for transfer with the Base64 Encoder/Decoder, reference permissions with the Linux Permissions Cheat Sheet, and keep our Linux Commands Cheat Sheet bookmarked.

Related Resources

Linux Commands Guide
Master the command line for remote server administration
Bash Scripting Guide
Automate tasks on the servers you manage over SSH
Nginx Configuration Guide
Configure web servers you manage through SSH connections
Docker Complete Guide
Deploy containers on servers you access via SSH
Linux Commands Cheat Sheet
Quick reference for essential Linux commands
Base64 Encoder/Decoder
Encode and decode data for secure transfer over SSH