TechEarl

SSH Cheat Sheet: Connect, Tunnel, Copy, and ssh-keygen Quick Reference

A scannable SSH reference: ssh-keygen, ssh-copy-id, port forwarding (-L, -R, -D), ProxyJump, ~/.ssh/config blocks, scp and rsync over SSH, with the Windows OpenSSH differences and PuTTY equivalents.

Ishan KarunaratneIshan Karunaratne⏱️ 13 min readUpdated
SSH cheat sheet: ssh-keygen ed25519, ssh-copy-id, local and remote port forwarding (-L, -R), SOCKS proxy (-D), ProxyJump, SSH config Host blocks, scp and rsync over SSH; Windows OpenSSH and PuTTY equivalents.

ssh is the universal remote-access tool: log in, copy files, forward ports, proxy traffic, jump through bastions. The flag surface is small, but the parts you only use occasionally (-L vs -R, ProxyJump syntax, ~/.ssh/config Host blocks) are the parts you forget. This page is the reference I keep open for the commands I do not type weekly, with the Windows OpenSSH and PuTTY equivalents where they differ.

How do I use SSH?

ssh connects to a remote machine over an encrypted channel, authenticates you (typically with a public/private keypair), and gives you a shell, a remote command execution, or a tunnel. The basic invocation is ssh user@host. Generate a keypair once with ssh-keygen -t ed25519, then install the public half on the server (ssh-copy-id user@host on Linux/macOS, or manually append ~/.ssh/authorized_keys on Windows). Once keys are in place, ssh user@host logs in without a password prompt. The same binary handles file copy (scp, sftp), port forwarding (-L for local, -R for remote, -D for SOCKS), and chained logins via bastion hosts (-J or ProxyJump). Configuration lives in ~/.ssh/config, where Host blocks let you alias frequently-used connections. Windows ships OpenSSH by default since Windows 10 1809; older systems used PuTTY (different keyfile format, different config UI).

Try it with your own values

Jump to:

Basic connection

bash· Linux (GNU)
ssh user@host.example.com

Specify a non-default port:

bash· Linux (GNU)
ssh -p 2222 user@host.example.com

Use a specific identity file (private key):

bash· Linux (GNU)
ssh -i ~/.ssh/id_ed25519_work user@host.example.com

Run a single command and exit:

bash· Linux (GNU)
ssh user@host.example.com 'uptime; df -h'

Verbose mode for debugging connection issues (add more v for more detail):

bash· Linux (GNU)
ssh -vvv user@host.example.com

Generating keys with ssh-keygen

Generate an Ed25519 keypair (the modern default; smaller, faster, and as secure as RSA-4096):

bash· Linux (GNU)
ssh-keygen -t ed25519 -C 'you@example.com'

You will be prompted for a save path (default ~/.ssh/id_ed25519) and an optional passphrase. The passphrase encrypts the private key at rest; combined with ssh-agent, you type it once per session.

RSA is still fine for older servers that do not understand Ed25519:

bash· Linux (GNU)
ssh-keygen -t rsa -b 4096 -C 'you@example.com'

Change or remove a passphrase on an existing key:

bash· Linux (GNU)
ssh-keygen -p -f ~/.ssh/id_ed25519

Print the key's fingerprint (compare with what the server displays on first connect):

bash· Linux (GNU)
ssh-keygen -lf ~/.ssh/id_ed25519.pub

Installing the public key

On Linux and macOS, ssh-copy-id is the one-shot answer:

bash· Linux (GNU)
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@host.example.com

ssh-copy-id is just a wrapper around the same append-to-authorized_keys logic the PowerShell variant does explicitly. macOS does not include ssh-copy-id by default in some versions, but brew install ssh-copy-id or the explicit cat ~/.ssh/id_ed25519.pub | ssh user@host 'cat >> ~/.ssh/authorized_keys' always works.

After installing the key, test:

bash· Linux (GNU)
ssh user@host.example.com

No password prompt means the key is working. If it still asks for a password, check permissions on the server: ~/.ssh must be 700, ~/.ssh/authorized_keys must be 600, and the user's home directory must not be world-writable.

Port forwarding

Three flavors, all useful, easy to mix up.

FlagDirectionUse case
-L LPORT:DEST:DPORTLocal-to-remoteAccess a remote service as if local
-R RPORT:DEST:DPORTRemote-to-localExpose a local service through the remote host
-D PORTDynamic (SOCKS)Tunnel arbitrary TCP through SSH

Local forward (-L) — reach a database on the remote network from your laptop:

bash· Linux (GNU)
ssh -L 5432:db.internal:5432 user@bastion.example.com

Connect a local Postgres client to localhost:5432 and the traffic flows over SSH to db.internal:5432.

Remote forward (-R) — expose your local dev server to the remote host:

bash· Linux (GNU)
ssh -R 8080:localhost:3000 user@host.example.com

Anyone on the remote host can hit http://localhost:8080 and reach your laptop's port 3000. Useful for sharing a local prototype with a remote teammate, or for webhook testing. For the latter, ngrok-style tools are usually easier; reverse SSH is the no-extra-account option.

Dynamic forward (-D) — a SOCKS proxy that tunnels arbitrary TCP through the SSH connection:

bash· Linux (GNU)
ssh -D 1080 user@host.example.com

Point your browser's SOCKS5 proxy at 127.0.0.1:1080 and every request flows through the remote host. Effectively a one-line VPN for HTTP traffic. Pairs with curl's --socks5 flag for command-line use.

Run forwards in the background with -fN (no remote command, fork into background):

bash· Linux (GNU)
ssh -fN -L 5432:db.internal:5432 user@bastion.example.com

ProxyJump and bastion hosts

-J chains SSH connections through one or more intermediate hosts. The traditional pattern was ssh -t bastion ssh target, which is fragile; ProxyJump is the modern, clean version.

bash· Linux (GNU)
ssh -J jump.example.com user@target.internal

Multiple hops, comma-separated:

bash· Linux (GNU)
ssh -J jump1.example.com,jump2.example.com user@target.internal

ProxyJump only opens a TCP channel through the jump host; it does not require a shell on the jump. The jump host needs OpenSSH 7.3+ on its sshd. The bigger win is that scp and rsync work transparently when ProxyJump is set in ~/.ssh/config.

The ~/.ssh/config file

Stop typing the same -J, -i, -p, -L flags. Put them in a Host block.

code
# ~/.ssh/config

Host bastion
    HostName bastion.example.com
    User ishan
    IdentityFile ~/.ssh/id_ed25519_work

Host prod-db
    HostName db.internal
    User dbadmin
    Port 22
    ProxyJump bastion
    LocalForward 5432 localhost:5432

Host github.com
    User git
    IdentityFile ~/.ssh/id_ed25519_github
    AddKeysToAgent yes
    UseKeychain yes        # macOS only: store passphrase in Keychain

Host *.staging.example.com
    User deploy
    IdentityFile ~/.ssh/id_ed25519_staging
    StrictHostKeyChecking accept-new

After saving:

bash· Linux (GNU)
ssh prod-db    # uses ProxyJump bastion, port 22, local forward, the lot

Key directives worth knowing:

DirectivePurpose
HostNameReal DNS name or IP (the Host alias is just for ssh invocation)
UserDefault username
PortNon-default SSH port
IdentityFileWhich private key to use
IdentitiesOnly yesUse ONLY the listed IdentityFile, ignore agent
ProxyJumpBastion host (replaces ProxyCommand for most uses)
LocalForward / RemoteForwardAuto-create -L / -R on connect
ServerAliveInterval 60Keep idle connections from dropping
AddKeysToAgent yesAuto-add the key to ssh-agent on first use
UseKeychain yesmacOS: store passphrase in Keychain
StrictHostKeyChecking accept-newAuto-accept new host keys but fail on changes (safer than no)

Config matches are first-match-wins; put specific Host blocks before wildcard blocks.

Copying files: scp and rsync

scp is the simplest, ships with OpenSSH, no setup:

bash· Linux (GNU)
scp local.txt user@host:/remote/path/

Download a remote file:

bash· Linux (GNU)
scp user@host:/remote/file.txt ./

Recursive directory copy:

bash· Linux (GNU)
scp -r ./dist user@host:/var/www/

rsync over SSH is faster for repeated syncs (delta transfers, resume, partial files):

bash· Linux (GNU)
rsync -avz --progress ./dist/ user@host:/var/www/

The trailing slash on the source matters: ./dist/ copies the contents, ./dist copies the directory itself. This is the source of more bad deployments than any other rsync quirk.

rsync over a specific SSH config / port:

bash· Linux (GNU)
rsync -avz -e 'ssh -p 2222 -i ~/.ssh/id_deploy' ./dist/ user@host:/var/www/

Worth noting: newer OpenSSH versions use the SFTP protocol under the hood for scp by default. If you hit "this protocol is not supported" errors, fall back to scp -O (capital O, legacy mode) or use sftp directly.

ssh-agent and ssh-add

ssh-agent keeps decrypted private keys in memory so you do not type the passphrase every connection. ssh-add loads keys into it.

Start the agent (Linux/macOS):

bash· Linux (GNU)
eval "$(ssh-agent -s)"

Add a key:

bash· Linux (GNU)
ssh-add ~/.ssh/id_ed25519

macOS-specific: --apple-use-keychain (modern OpenSSH on macOS) or -K (older) stores the passphrase in the Keychain so it persists across logins.

List loaded keys:

bash· Linux (GNU)
ssh-add -l

Remove all keys from the agent:

bash· Linux (GNU)
ssh-add -D

Windows specifics: OpenSSH and PuTTY

Two worlds: native Windows OpenSSH (Windows 10 1809+, Windows Server 2019+) and PuTTY (the historical default).

ConcernWindows OpenSSHPuTTY
ssh commandAvailable in PowerShell and cmdplink.exe (CLI) and putty.exe (GUI)
Private key formatOpenSSH (id_ed25519)PuTTY's own .ppk format
Key conversionssh-keygen -e -f and -i -fputtygen (Load OpenSSH, Save .ppk)
Config file%USERPROFILE%\.ssh\configSaved sessions in registry
Agentssh-agent service (built-in)Pageant
ssh-copy-idNot availableManual via PuTTY's Pageant

If you are coming from PuTTY and moving to native OpenSSH, see how to export and import PuTTY settings for migrating saved sessions and key conversion.

Install OpenSSH on Windows (if not already present):

powershell
Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0

The client is on by default on Windows 11 and recent Windows 10. The server (sshd) is optional.

Common pitfalls

1. Permissions on ~/.ssh are too loose. sshd refuses to use a key if the file is group- or world-readable. Fix: chmod 700 ~/.ssh, chmod 600 ~/.ssh/id_*, chmod 644 ~/.ssh/*.pub. The ~ home directory itself must not be group-writable either.

2. Host key changed. "WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!" usually means the server was rebuilt and presents a new key. If you trust the rebuild, remove the stale entry: ssh-keygen -R hostname. If you do not know why it changed, do NOT just delete it.

3. Forgetting -N with -f forwards a port AND opens an unwanted shell. Use -fN for background forwards: -f forks to background, -N says "do not run a remote command", together they keep just the forward alive.

4. ProxyJump on the wrong sshd version. -J requires OpenSSH 7.3 or later on the jump host. Older servers need the legacy ProxyCommand ssh -W %h:%p jump pattern.

5. ssh-add cleared on every reboot. macOS Keychain integration requires UseKeychain yes in ~/.ssh/config AND --apple-use-keychain (or legacy -K) when adding. Linux needs AddKeysToAgent yes in config plus a session-managed agent (gnome-keyring, KDE Wallet, etc.).

6. SSH timeouts on idle connections. NAT routers and load balancers drop idle TCP after a few minutes. Set ServerAliveInterval 60 and ServerAliveCountMax 3 in ~/.ssh/config to send keepalives.

7. scp with newer OpenSSH errors out. Recent versions deprecate the legacy SCP protocol. Fall back with scp -O or switch to sftp / rsync.

8. Windows OpenSSH does not support -f (background). Run forwards in a separate PowerShell window, or use Start-Process to background the whole ssh invocation.

What to do next

FAQ

TagsSSHssh-keygenPort ForwardingscprsyncPuTTYOpenSSHDevOpsNetworking
Share
Ishan Karunaratne

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years across software, Linux systems, DevOps, and infrastructure — and a more recent focus on AI. Currently Chief Technology Officer at a tech startup in the healthcare space.

Keep reading

Related posts