Getting started
Basic connection
ssh user@host # Connect to host
ssh host # Use current username
ssh -p 2222 user@host # Custom port
ssh user@host command # Run command and exit
Common options
ssh -v user@host # Verbose (debugging)
ssh -vv user@host # More verbose
ssh -q user@host # Quiet mode
ssh -4 user@host # Force IPv4
ssh -6 user@host # Force IPv6
ssh -C user@host # Enable compression
With identity file
ssh -i ~/.ssh/key user@host # Use specific key
ssh -i key.pem ec2-user@aws # AWS EC2 example
SSH keys
Generate keys
# RSA (default 3072-bit)
ssh-keygen
# Ed25519 (recommended, faster, more secure)
ssh-keygen -t ed25519 -C "email@example.com"
# RSA with 4096 bits
ssh-keygen -t rsa -b 4096 -C "email@example.com"
# With custom filename
ssh-keygen -f ~/.ssh/custom_key
Copy key to server
# Automatic (recommended)
ssh-copy-id user@host
# With custom key
ssh-copy-id -i ~/.ssh/custom_key.pub user@host
# Manual (if ssh-copy-id unavailable)
cat ~/.ssh/id_ed25519.pub | ssh user@host "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
Key management
# View public key
cat ~/.ssh/id_ed25519.pub
# Change key passphrase
ssh-keygen -p -f ~/.ssh/id_ed25519
# List key fingerprints
ssh-keygen -lf ~/.ssh/id_ed25519.pub
# Test key authentication
ssh -i ~/.ssh/id_ed25519 -T git@github.com
Config file (~/.ssh/config)
Basic host entry
Host myserver
HostName example.com
User john
Port 2222
IdentityFile ~/.ssh/id_ed25519
Usage: ssh myserver
Multiple hosts with wildcard
Host dev-*
User developer
IdentityFile ~/.ssh/dev_key
Host prod-*
User admin
IdentityFile ~/.ssh/prod_key
Host *.example.com
Port 2222
Common directives
| Directive | Description |
|---|---|
HostName |
Real hostname or IP |
User |
Username for connection |
Port |
SSH port (default 22) |
IdentityFile |
Private key path |
ForwardAgent |
Enable agent forwarding |
ServerAliveInterval |
Keepalive (seconds) |
ServerAliveCountMax |
Max keepalive packets |
ProxyJump |
Jump/bastion host |
LocalForward |
Local port forward |
RemoteForward |
Remote port forward |
DynamicForward |
SOCKS proxy |
ControlMaster |
Connection multiplexing |
ControlPath |
Socket path for multiplexing |
ControlPersist |
Keep connection open (duration) |
Connection keepalive
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
Keeps connection alive, disconnects after 3 missed keepalives.
Connection multiplexing
Host *
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
Reuses existing connections for speed. Create socket dir: mkdir -p ~/.ssh/sockets
Port forwarding
Local forwarding (L)
# Forward local port to remote
ssh -L 8080:localhost:80 user@host
# Access remote service locally
ssh -L 5432:db.internal:5432 user@bastion
# Now: psql -h localhost -p 5432
Pattern: -L local_port:remote_host:remote_port
Remote forwarding (R)
# Forward remote port to local
ssh -R 9000:localhost:3000 user@host
# Remote users access localhost:9000 → your localhost:3000
# Share local web server
ssh -R 8080:localhost:80 user@host
Pattern: -R remote_port:local_host:local_port
Dynamic forwarding (SOCKS proxy)
# Create SOCKS proxy
ssh -D 1080 user@host
# Use with browser or tools
curl --proxy socks5h://localhost:1080 https://example.com
# With Firefox: Set SOCKS proxy to localhost:1080
Background forwarding
# Run in background
ssh -fN -L 8080:localhost:80 user@host
# Options:
# -f: Background before command execution
# -N: Don't execute remote command
Config file forwarding
Host tunnel
HostName example.com
User john
LocalForward 8080 localhost:80
LocalForward 5432 db.internal:5432
Host proxy
HostName example.com
User john
DynamicForward 1080
ProxyJump & bastion hosts
Single jump host
# Command line
ssh -J bastion user@internal
# Multiple jumps
ssh -J jump1,jump2 user@target
# With ports
ssh -J user@jump:2222 user@target
Config file ProxyJump
Host internal-server
HostName 10.0.1.50
User admin
ProxyJump bastion
Host bastion
HostName bastion.example.com
User jumpuser
Usage: ssh internal-server
Multi-hop with ProxyCommand (legacy)
Host target
HostName 10.0.1.50
User admin
ProxyCommand ssh -W %h:%p bastion
Host bastion
HostName bastion.example.com
User jumpuser
Port forward through jump host
# Local forward through bastion
ssh -J bastion -L 5432:db.internal:5432 user@target
ssh-agent
Start agent
# Start in current shell
eval $(ssh-agent)
# Check if running
echo $SSH_AGENT_PID
# List loaded keys
ssh-add -l
Add keys
# Add default keys (~/.ssh/id_*)
ssh-add
# Add specific key
ssh-add ~/.ssh/custom_key
# Add with timeout (seconds)
ssh-add -t 3600 ~/.ssh/id_ed25519
# Add all keys in ~/.ssh/
ssh-add ~/.ssh/id_*
Remove keys
# Remove specific key
ssh-add -d ~/.ssh/id_ed25519
# Remove all keys
ssh-add -D
Agent forwarding
# Command line
ssh -A user@host
# Config file
Host jumphost
ForwardAgent yes
⚠️ Security warning: Only forward agent to trusted hosts. Compromised jump host can use your keys.
macOS Keychain integration
Host *
UseKeychain yes
AddKeysToAgent yes
IdentityFile ~/.ssh/id_ed25519
Stores passphrase in macOS Keychain.
File transfer
SCP (Secure Copy)
# Copy to remote
scp file.txt user@host:/path/
# Copy from remote
scp user@host:/path/file.txt .
# Copy directory (recursive)
scp -r directory/ user@host:/path/
# With custom port
scp -P 2222 file.txt user@host:/path/
# With custom key
scp -i ~/.ssh/key file.txt user@host:/path/
# Copy between remote hosts (via local)
scp user1@host1:/file user2@host2:/path/
SCP with ProxyJump
# Through bastion
scp -J bastion file.txt user@internal:/path/
# Through multiple jumps
scp -J jump1,jump2 user@target:/file .
SFTP (Interactive)
# Connect
sftp user@host
# Common SFTP commands
get remote_file # Download
put local_file # Upload
get -r directory # Download directory
put -r directory # Upload directory
ls # List remote
lls # List local
cd /remote/path # Change remote dir
lcd /local/path # Change local dir
pwd # Remote working dir
lpwd # Local working dir
mkdir dirname # Create remote dir
rmdir dirname # Remove remote dir
rm filename # Delete remote file
bye # Exit
SFTP batch mode
# Execute commands from file
sftp -b commands.txt user@host
# Echo commands via pipe
echo "get /remote/file" | sftp user@host
# One-liner
sftp user@host <<< "get /remote/file"
rsync over SSH (recommended)
# Sync directory (more efficient than scp)
rsync -avz -e ssh source/ user@host:/dest/
# Options:
# -a: Archive mode (preserves permissions, times)
# -v: Verbose
# -z: Compression
# -e ssh: Use SSH
# With progress
rsync -avzP -e ssh source/ user@host:/dest/
# With custom port
rsync -avz -e "ssh -p 2222" source/ user@host:/dest/
Security best practices
Key-based authentication
# Disable password authentication (server-side)
# /etc/ssh/sshd_config:
PasswordAuthentication no
PubkeyAuthentication yes
PermitRootLogin prohibit-password
Hardening SSH config
# Client-side (~/.ssh/config)
Host *
# Only use key auth
PreferredAuthentications publickey
# Use modern algorithms only
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
# Verify host keys strictly
StrictHostKeyChecking ask
HashKnownHosts yes
Server hardening
# Server-side /etc/ssh/sshd_config
Port 2222 # Non-default port
PermitRootLogin no # Disable root login
MaxAuthTries 3 # Limit auth attempts
PubkeyAuthentication yes
PasswordAuthentication no
PermitEmptyPasswords no
X11Forwarding no # Disable if unused
AllowUsers user1 user2 # Whitelist users
Fail2ban integration
# Install fail2ban (Ubuntu/Debian)
sudo apt install fail2ban
# Enable SSH jail
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Automatically bans IPs after failed login attempts.
Key file permissions
# Private key: Only owner can read/write
chmod 600 ~/.ssh/id_ed25519
# Public key: Owner read/write, others read
chmod 644 ~/.ssh/id_ed25519.pub
# SSH directory
chmod 700 ~/.ssh
# Authorized keys
chmod 600 ~/.ssh/authorized_keys
# Config file
chmod 600 ~/.ssh/config
⚠️ SSH will refuse to use keys with incorrect permissions.
Troubleshooting
Verbose debugging
# Level 1 (basic)
ssh -v user@host
# Level 2 (detailed)
ssh -vv user@host
# Level 3 (maximum)
ssh -vvv user@host
Look for: "debug1:", "debug2:", "debug3:" lines
Common issues
| Problem | Solution |
|---|---|
| Permission denied (publickey) | Check key permissions (600) |
Verify key in authorized_keys |
|
Check ssh-add -l (agent) |
|
| Connection refused | Check port (default 22) |
| Verify SSH service running | |
| Connection timeout | Check firewall rules |
Verify host reachable (ping) |
|
| Host key verification failed | Remove old key: ssh-keygen -R host |
| Agent forwarding not working | Use -A flag or ForwardAgent yes |
| Too many authentication fails | Limit keys: ssh -o IdentitiesOnly=yes |
Check server logs
# On server, check SSH logs
sudo tail -f /var/log/auth.log # Debian/Ubuntu
sudo tail -f /var/log/secure # RHEL/CentOS
# Check SSH service status
sudo systemctl status sshd
sudo systemctl status ssh # Debian/Ubuntu
Test configuration
# Test config file syntax (client)
ssh -G user@host
# Test SSH server config (server)
sudo sshd -t
# Check which config is used
ssh -v user@host 2>&1 | grep "Reading configuration"
Remove known host entry
# After "Host key verification failed"
ssh-keygen -R hostname
# Remove by line number
ssh-keygen -R hostname -f ~/.ssh/known_hosts
# Remove by IP
ssh-keygen -R 192.168.1.100
Key not being offered
# Force use of specific key
ssh -o IdentitiesOnly=yes -i ~/.ssh/key user@host
# Check which keys are tried
ssh -v user@host 2>&1 | grep "identity file"
Advanced usage
Execute remote commands
# Single command
ssh user@host "df -h"
# Multiple commands
ssh user@host "cd /app && ./deploy.sh"
# Sudo command (may need tty)
ssh -t user@host "sudo systemctl restart nginx"
# With heredoc
ssh user@host bash <<'EOF'
cd /app
git pull
npm install
pm2 restart all
EOF
Escape sequences
Press Enter then ~ then:
| Sequence | Action |
|---|---|
~. |
Terminate connection |
~^Z |
Suspend connection |
~# |
List forwarded connections |
~& |
Background connection |
~? |
Show help |
~~ |
Send literal ~ |
X11 forwarding
# Enable X11 forwarding
ssh -X user@host
# Trusted X11 forwarding (faster)
ssh -Y user@host
# Run GUI app
ssh -X user@host firefox
Config file:
Host trusted
ForwardX11 yes
ForwardX11Trusted yes
Reverse tunnel for remote access
# On server behind NAT
ssh -R 2222:localhost:22 user@public-server
# From public server, access NAT server
ssh -p 2222 user@localhost
Use case: Access home server from anywhere.
SSHFS (mount remote filesystem)
# Install
sudo apt install sshfs # Ubuntu/Debian
brew install macfuse sshfs # macOS
# Mount
sshfs user@host:/remote/path /local/mount
# Unmount
fusermount -u /local/mount # Linux
umount /local/mount # macOS
SSH jump with command
# Run command on target through jump
ssh -J bastion user@target "hostname"
# Port forward through jump
ssh -J bastion -L 8080:internal:80 user@target
Persistent reverse tunnels (AutoSSH)
# Install
sudo apt install autossh
# Keep tunnel alive
autossh -M 0 -N -R 2222:localhost:22 user@host
# systemd service for persistent tunnel
# /etc/systemd/system/reverse-tunnel.service
Gotchas & tips
SSH config precedence
Config rules apply in order of first match. Put specific rules before general ones:
# ✅ Correct order
Host prod-db
Port 5432
Host prod-*
Port 2222
# ❌ Wrong: prod-db will use port 2222
Host prod-*
Port 2222
Host prod-db
Port 5432
Permissions issues
⚠️ SSH is strict about permissions. If keys aren't working, check:
~/.ssh/must be700(drwx------)- Private keys must be
600(-rw-------) authorized_keysmust be600(-rw-------)- Config must be
600(-rw-------)
Agent forwarding security
⚠️ Don't use ForwardAgent yes globally:
# ❌ Bad: Forwards to all hosts
Host *
ForwardAgent yes
# ✅ Good: Only to trusted jump hosts
Host bastion.trusted.com
ForwardAgent yes
ProxyJump vs ProxyCommand
- ProxyJump (
-J): Modern, simpler syntax (OpenSSH 7.3+) - ProxyCommand: Legacy, more flexible, works on older SSH
Prefer ProxyJump unless you need complex proxy logic.
ControlMaster socket cleanup
Stale sockets can prevent connections. Clean up:
# Remove stale sockets
rm -f ~/.ssh/sockets/*
# Or add cleanup to config
Host *
ControlPath ~/.ssh/sockets/%C
%C uses hash, avoiding path length limits.
SSH through HTTP proxy
For restrictive corporate networks:
# Install corkscrew
sudo apt install corkscrew
# Config file
Host external
ProxyCommand corkscrew proxy.corp.com 8080 %h %p
Connection hangs on exit
If connection hangs after command completes, try:
ssh user@host "command; exit"
# Or in config
Host problematic-host
ServerAliveInterval 30
Also see
- OpenSSH official manual (openssh.com)
- ssh_config man page (man.openbsd.org)
- SSH Academy (ssh.com)
- GitHub SSH guide (github.com)
- Mozilla SSH guidelines (mozilla.org)
- DigitalOcean SSH tutorial (digitalocean.com)