Skip to content

SSH Cheat Sheet

Fun fact: SSH (Secure Shell) was created by Tatu Ylonen in 1995 at Helsinki University of Technology after a password-sniffing attack on the university network. It replaced telnet, rsh, and rlogin — all of which sent credentials in plaintext. SSH uses port 22 because Ylonen noticed ports 21 (FTP) and 23 (Telnet) were taken, so he requested port 22 from IANA and got it approved the same day.

Remember: SSH debug verbosity levels: -v (basic handshake), -vv (key exchange details), -vvv (full packet-level debug). Start with -v and increase only if needed. For 99% of connection issues, -v shows the problem: wrong key offered, host key mismatch, or authentication method rejected.

Basic Connection

ssh user@host                         # Connect with default key
ssh -p 2222 user@host                 # Non-default port
ssh -i ~/.ssh/mykey user@host         # Specific private key
ssh -v user@host                      # Verbose (debug connection issues)
ssh -vvv user@host                    # Maximum verbosity

Common Flags

Flag Meaning
-p PORT Connect to non-standard port
-i KEY Use specific identity file
-v / -vv / -vvv Increasing verbosity levels
-o OPTION=VALUE Set config option inline
-N No remote command (tunnels only)
-f Go to background after auth
-q Quiet mode (suppress warnings)
-T Disable pseudo-terminal allocation
-t Force pseudo-terminal allocation
-C Enable compression
-A Enable agent forwarding
-J JUMP_HOST ProxyJump through a bastion

Key Management

# Generate a key pair (Ed25519 preferred)
ssh-keygen -t ed25519 -C "user@host" -f ~/.ssh/mykey

# Generate RSA key (when Ed25519 not supported)
ssh-keygen -t rsa -b 4096 -C "user@host"

# Copy public key to remote host
ssh-copy-id -i ~/.ssh/mykey.pub user@host

# View key fingerprint
ssh-keygen -lf ~/.ssh/mykey.pub

# Change passphrase on existing key
ssh-keygen -p -f ~/.ssh/mykey

# Convert key format (OpenSSH <-> PEM)
ssh-keygen -p -m PEM -f ~/.ssh/mykey      # To PEM
ssh-keygen -p -m RFC4716 -f ~/.ssh/mykey   # To RFC4716

SSH Agent

# Start the agent
eval "$(ssh-agent -s)"

# Add key to agent
ssh-add ~/.ssh/mykey

# Add key with timeout (seconds)
ssh-add -t 3600 ~/.ssh/mykey

# List loaded keys
ssh-add -l

# Remove all keys from agent
ssh-add -D

# Forward agent to remote host (use with caution)
ssh -A user@host

SSH Config File (~/.ssh/config)

# Default for all hosts
Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3
    AddKeysToAgent yes

# Named host
Host prod-bastion
    HostName 10.0.1.50
    User admin
    Port 2222
    IdentityFile ~/.ssh/prod-key

# Jump through bastion
Host prod-app
    HostName 10.0.2.100
    User deploy
    ProxyJump prod-bastion

# Wildcard pattern
Host *.staging.example.com
    User staging
    IdentityFile ~/.ssh/staging-key
    StrictHostKeyChecking no
    UserKnownHostsFile /dev/null

Tunneling (Port Forwarding)

# Local port forward: access remote:5432 via localhost:5432
ssh -L 5432:db.internal:5432 user@bastion

# Local forward (background, no shell)
ssh -fNL 5432:db.internal:5432 user@bastion

# Remote port forward: expose local:3000 on remote:8080
ssh -R 8080:localhost:3000 user@remote

# Dynamic SOCKS proxy
ssh -D 1080 user@host
# Then configure browser/app to use SOCKS5 proxy at localhost:1080

# Tunnel through multiple hops
ssh -J bastion1,bastion2 user@target

ProxyJump (Bastion/Jump Host)

# Command line
ssh -J user@bastion user@internal-host

# Multiple jumps
ssh -J bastion1,bastion2 user@target

# In SSH config (preferred)
Host internal
    HostName 10.0.2.50
    ProxyJump bastion

# Legacy ProxyCommand equivalent
Host internal
    HostName 10.0.2.50
    ProxyCommand ssh -W %h:%p bastion

File Transfer

# SCP: copy file to remote
scp file.txt user@host:/path/to/dest/

# SCP: copy from remote
scp user@host:/path/to/file.txt ./

# SCP: recursive directory copy
scp -r mydir/ user@host:/path/to/dest/

# Rsync over SSH (preferred over SCP)
rsync -avz -e ssh localdir/ user@host:/remotedir/

# SFTP interactive session
sftp user@host

Troubleshooting

# Test connection without login
ssh -o BatchMode=yes -o ConnectTimeout=5 user@host echo ok

# Check which key the server accepted
ssh -v user@host 2>&1 | grep "Authenticated"

# Debug permission denied
ssh -vvv user@host 2>&1 | grep -A2 "Trying\|Offering\|Server accepts"

# Check sshd config syntax
sudo sshd -t

# View active SSH sessions on a server
who | grep pts
ss -tnp | grep :22

# Check known_hosts for a host
ssh-keygen -F hostname

# Remove old host key (after server rebuild)
ssh-keygen -R hostname

Security Best Practices

# Disable password auth (in /etc/ssh/sshd_config)
PasswordAuthentication no
ChallengeResponseAuthentication no

# Restrict to specific users
AllowUsers deploy admin

# Disable root login
PermitRootLogin no

# Use strong key exchange algorithms
KexAlgorithms curve25519-sha256@libssh.org

# Limit auth attempts
MaxAuthTries 3

# Set idle timeout
ClientAliveInterval 300
ClientAliveCountMax 2

Quick Recipes

# Run remote command and exit
ssh user@host 'df -h && free -m'

# Run local script on remote host
ssh user@host 'bash -s' < local-script.sh

# Multiplex connections (ControlMaster)
# In ~/.ssh/config:
Host *
    ControlMaster auto
    ControlPath ~/.ssh/sockets/%r@%h-%p
    ControlPersist 600

# Create the socket directory
mkdir -p ~/.ssh/sockets

# Kill a stuck SSH session
# Press: Enter, ~, .