Toolmingo
Guides9 min read

SSH Cheat Sheet: Every Command You Actually Need

A complete SSH cheat sheet — connecting, key management, tunnels, SCP, config file, port forwarding, and security hardening. Copy-ready commands for daily server work.

The commands you reach for when connecting to servers, copying files, or setting up tunnels — and the ones you Google every time. This reference covers authentication, key management, config file shortcuts, tunnelling, and security.

Quick reference

The 25 SSH operations that cover 90% of daily work.

Command What it does
ssh user@host Connect to host as user
ssh -p 2222 user@host Connect on non-default port
ssh-keygen -t ed25519 Generate a new key pair
ssh-copy-id user@host Copy public key to server
ssh -i ~/.ssh/id_ed25519 user@host Use a specific key
ssh -A user@host Enable agent forwarding
ssh -N -L 8080:localhost:80 user@host Local port forward
ssh -N -R 9090:localhost:3000 user@host Remote port forward
ssh -D 1080 user@host Dynamic SOCKS proxy
scp file.txt user@host:/path/ Copy file to server
scp user@host:/path/file.txt . Copy file from server
scp -r dir/ user@host:/path/ Copy directory recursively
rsync -avz dir/ user@host:/path/ Sync directory to server
ssh-add ~/.ssh/id_ed25519 Add key to agent
ssh-add -l List keys in agent
ssh-add -D Remove all keys from agent
ssh-keyscan host Fetch host public key
ssh -v user@host Verbose debug output
ssh -vvv user@host Maximum debug output
ssh user@host 'command' Run command and exit
ssh -t user@host 'sudo bash' Force TTY allocation
cat ~/.ssh/id_ed25519.pub Print your public key
ssh-keygen -lf ~/.ssh/id_ed25519.pub Show key fingerprint
ssh-keygen -R hostname Remove known host entry
kill $(pgrep -f 'ssh -N') Kill background tunnels

Connecting

Basic connection

# Connect as current local user
ssh hostname

# Connect as specific user
ssh user@hostname

# Connect on a custom port
ssh -p 2222 user@hostname

# Connect using a specific identity file
ssh -i ~/.ssh/deploy_key user@hostname

# Connect and run a command, then exit
ssh user@hostname 'ls -la /var/www'

# Interactive remote shell (force TTY for sudo)
ssh -t user@hostname 'sudo systemctl status nginx'

Multiple hops (ProxyJump)

# Jump through a bastion host
ssh -J bastion.example.com user@internal-server

# Multiple hops
ssh -J user@bastion,user@jump2 user@final-host

# In SSH config (cleaner):
# Host internal
#   ProxyJump bastion.example.com

Escape sequences (while connected)

Sequence Action
~. Disconnect (kill session)
~C Open SSH command line
~& Backgrounding
~? List escape sequences

Press Enter first, then the sequence.

Key management

Generate a key pair

# Ed25519 (recommended — modern, fast, secure)
ssh-keygen -t ed25519 -C "your@email.com"

# RSA 4096 (for legacy servers that don't support Ed25519)
ssh-keygen -t rsa -b 4096 -C "your@email.com"

# Custom filename
ssh-keygen -t ed25519 -f ~/.ssh/deploy_key -C "deploy key for prod"

# Without passphrase (for automation)
ssh-keygen -t ed25519 -N "" -f ~/.ssh/ci_key

Copy your public key to a server

# Automatic (preferred)
ssh-copy-id user@hostname

# With specific key
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@hostname

# Manual (if ssh-copy-id unavailable)
cat ~/.ssh/id_ed25519.pub | ssh user@hostname 'mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys'

SSH agent

# Start the agent (usually auto-started by desktop/shell)
eval "$(ssh-agent -s)"

# Add your key to the agent
ssh-add ~/.ssh/id_ed25519

# Add with expiry (remove after 4 hours)
ssh-add -t 4h ~/.ssh/id_ed25519

# List loaded keys
ssh-add -l

# Remove a specific key
ssh-add -d ~/.ssh/id_ed25519

# Remove all keys
ssh-add -D

Inspect and convert keys

# Show fingerprint of a public key
ssh-keygen -lf ~/.ssh/id_ed25519.pub

# Show fingerprint in MD5 format (legacy)
ssh-keygen -E md5 -lf ~/.ssh/id_ed25519.pub

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

# Convert OpenSSH private key to PEM format (for older tools)
ssh-keygen -e -p -m pem -f ~/.ssh/id_rsa

# Print the public key from a private key
ssh-keygen -y -f ~/.ssh/id_ed25519

SSH config file

~/.ssh/config eliminates typing the same flags repeatedly.

Config syntax

Host alias
    HostName actual.hostname.com
    User     remote_user
    Port     2222
    IdentityFile ~/.ssh/special_key

Then connect with just: ssh alias

Common config patterns

# Production server with non-standard port and key
Host prod
    HostName 203.0.113.10
    User     deploy
    Port     2222
    IdentityFile ~/.ssh/prod_key

# Staging via bastion
Host staging
    HostName 10.0.0.50
    User     ubuntu
    ProxyJump bastion.example.com

# Bastion / jump server
Host bastion
    HostName bastion.example.com
    User     ec2-user
    IdentityFile ~/.ssh/bastion_key
    ForwardAgent yes

# GitHub (if using non-default key)
Host github.com
    User        git
    IdentityFile ~/.ssh/github_key

# Wildcard for a subnet
Host 10.0.0.*
    User         ubuntu
    IdentityFile ~/.ssh/internal_key
    StrictHostKeyChecking no  # only for trusted private nets

# Keep connections alive
Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3
    AddKeysToAgent yes

Config directives reference

Directive Purpose
HostName Real hostname/IP
User Remote username
Port Port number
IdentityFile Path to private key
ProxyJump Jump host(s)
ForwardAgent Forward SSH agent
ServerAliveInterval Keepalive ping interval (seconds)
ServerAliveCountMax Max missed keepalives before disconnect
StrictHostKeyChecking yes/no/accept-new
AddKeysToAgent Auto-add key to agent on use
ControlMaster Multiplex connections
ControlPath Socket path for multiplexing
ControlPersist Keep master open after client exits

File transfer

SCP (secure copy)

# Upload: local → server
scp file.txt user@host:/remote/path/
scp file.txt user@host:~/          # to home dir

# Download: server → local
scp user@host:/remote/file.txt .
scp user@host:~/logs/app.log ./logs/

# Copy directory (-r recursive)
scp -r ./dist/ user@host:/var/www/app/

# Preserve timestamps (-p)
scp -p file.txt user@host:/path/

# Custom port (-P — note uppercase)
scp -P 2222 file.txt user@host:/path/

# Limit bandwidth to 1 Mbit/s
scp -l 1000 large.tar.gz user@host:/backups/

rsync (preferred for directories)

# Sync local dir to server (trailing slash = contents, not dir)
rsync -avz ./dist/ user@host:/var/www/app/

# Dry run (show what would change)
rsync -avzn ./dist/ user@host:/var/www/app/

# Delete files on destination that don't exist locally
rsync -avz --delete ./dist/ user@host:/var/www/

# Exclude patterns
rsync -avz --exclude='node_modules' --exclude='*.log' ./ user@host:/app/

# Download from server
rsync -avz user@host:/var/log/nginx/ ./logs/

# Non-default port
rsync -avz -e 'ssh -p 2222' ./dist/ user@host:/var/www/

# Bandwidth limit (50 KB/s)
rsync -avz --bwlimit=50 large/ user@host:/backup/

Port forwarding and tunnels

Local port forwarding

Makes a remote port available locally. Traffic goes: localhost:LOCAL → server → DESTINATION:DEST_PORT.

# Access a remote database locally
ssh -N -L 5432:localhost:5432 user@db-server
# Then connect: psql -h 127.0.0.1 -p 5432

# Access an internal HTTP service
ssh -N -L 8080:internal-app:80 user@jump-server
# Then open: http://localhost:8080

# -N: don't execute a remote command
# -f: background the process
ssh -f -N -L 8080:localhost:80 user@server

Remote port forwarding

Makes a local port available on the remote server. Traffic goes: server:REMOTE_PORT → localhost:LOCAL_PORT.

# Expose local dev server to the remote machine
ssh -N -R 9000:localhost:3000 user@server
# From server: curl http://localhost:9000

# Expose to all interfaces on the remote (requires GatewayPorts yes in sshd_config)
ssh -N -R 0.0.0.0:9000:localhost:3000 user@server

Dynamic (SOCKS) proxy

# SOCKS5 proxy on local port 1080, tunnelled through server
ssh -N -D 1080 user@server

# Then configure your browser or tool to use SOCKS5 proxy:
# Host: 127.0.0.1, Port: 1080

# Use with curl
curl --socks5 127.0.0.1:1080 https://internal.service.com

Connection multiplexing (faster reconnects)

# In ~/.ssh/config — reuse existing connection
Host myserver
    ControlMaster   auto
    ControlPath     ~/.ssh/cm-%r@%h:%p
    ControlPersist  10m

After the first ssh myserver, subsequent connections open instantly without re-authenticating.

Known hosts

# View known hosts file
cat ~/.ssh/known_hosts

# Add a host key without connecting
ssh-keyscan -H hostname >> ~/.ssh/known_hosts

# Remove a stale or changed host entry (do this when server is reinstalled)
ssh-keygen -R hostname
ssh-keygen -R 203.0.113.10

# Scan a non-default port
ssh-keyscan -p 2222 -H hostname >> ~/.ssh/known_hosts

Server-side: sshd_config

Common settings in /etc/ssh/sshd_config (apply with systemctl reload sshd):

# Disable password login (key-only)
PasswordAuthentication no

# Disable root login
PermitRootLogin no

# Change default port
Port 2222

# Limit to specific users
AllowUsers deploy ubuntu

# Allow specific groups
AllowGroups sshusers sudo

# Idle timeout: disconnect after 10 min idle
ClientAliveInterval 300
ClientAliveCountMax 2

# Allow agent forwarding from trusted users
AllowAgentForwarding yes

# Allow remote port forwarding to bind on all interfaces
GatewayPorts yes

# Restrict to specific interface/IP
ListenAddress 0.0.0.0

Debugging connection issues

# Verbose output (one level)
ssh -v user@host

# Maximum verbosity (three levels) — shows key negotiation
ssh -vvv user@host

# Test sshd config for syntax errors
sudo sshd -t

# Check sshd log (Debian/Ubuntu)
sudo journalctl -u ssh -f

# Check sshd log (RHEL/CentOS)
sudo journalctl -u sshd -f

# Check if sshd is listening
ss -tlnp | grep :22

# Check authorized_keys permissions (common issue)
ls -la ~/.ssh/
# .ssh must be 700, authorized_keys must be 600
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

Common mistakes

Mistake Problem Fix
Wrong permissions on ~/.ssh sshd silently ignores authorized_keys chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys
Passphrase on key, no agent Prompted every connection ssh-add ~/.ssh/id_ed25519 or use AddKeysToAgent yes in config
Copying entire private key Leaks credentials Only share ~/.ssh/id_ed25519.pub (the .pub file)
-P vs -p for port scp uses -P (uppercase), ssh uses -p (lowercase) Remember: SCP Uppercase Port
scp for large syncs Re-sends unchanged files Use rsync instead
Agent forwarding on untrusted hosts Root on server can hijack your agent Only use -A on servers you control
Hardcoding IPs in commands Breaks when server IP changes Use ~/.ssh/config aliases

FAQ

Why does SSH ask for a password even though I set up a key?

Usually a permissions problem. The server requires ~/.ssh to be 700 and ~/.ssh/authorized_keys to be 600 (no group/world write). Also check that the public key was appended correctly — each key must be on its own line.

What's the difference between ssh-keygen -t ed25519 and rsa?

Ed25519 is a modern elliptic-curve algorithm: shorter keys, faster operations, and equally secure. Use Ed25519 unless you need to support very old SSH servers (OpenSSH < 6.5 or legacy network appliances), which only accept RSA.

How do I run multiple commands over SSH?

# Semicolon-separated (all run regardless of failure)
ssh user@host 'cd /app && git pull && npm install && pm2 restart app'

# Here-doc for multi-line scripts
ssh user@host << 'EOF'
  cd /app
  git pull
  npm ci
  pm2 restart app
EOF

How do I keep an SSH connection from timing out?

Either client-side (~/.ssh/config):

Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3

Or server-side (/etc/ssh/sshd_config):

ClientAliveInterval 300
ClientAliveCountMax 2

What's the safest way to set up passwordless SSH for CI/CD?

  1. Generate a dedicated key with no passphrase: ssh-keygen -t ed25519 -N "" -f ~/.ssh/ci_key
  2. Add the public key to the server's authorized_keys
  3. Restrict what the key can do with a from + command prefix in authorized_keys:
    from="10.0.0.0/8",command="/usr/bin/deploy.sh" ssh-ed25519 AAAA...
    
  4. Store the private key as a secret in your CI system (GitHub Actions secret, GitLab CI variable, etc.)

How do I forward X11 (GUI apps over SSH)?

ssh -X user@host        # X11 forwarding (basic)
ssh -Y user@host        # Trusted X11 forwarding (faster, less secure)
# Then run: firefox &

Requires X11Forwarding yes in sshd_config and an X server on your local machine (XQuartz on macOS, VcXsrv/WSLg on Windows).

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools