SSH Cheatsheet - SSH Connection & Configuration Command Reference
For developers and ops who want passwordless login, hop through bastion hosts into the internal network, or map a remote port to localhost for debugging. The value of SSH lies in reusing keys and config: pin frequently used Hosts, jump hosts, and port forwards into ~/.ssh/config instead of retyping long argument sets each time. By the end you can generate and correctly permission a key pair for passwordless login, hop through bastions with ProxyJump, and expose a remote service on localhost with -L for debugging.
Typical Use Case
Remote login and file transfer, passwordless key-based login, port forwarding (tunnels), and troubleshooting connection timeouts, permission-too-open rejections, and host key change alerts.
Connection & Auth 5
ssh user@hostssh -p 2222 user@hostssh -i ~/.ssh/id_ed25519 user@hostssh -vvv user@hostssh -o ConnectTimeout=5 user@hostKey Management 5
ssh-keygen -t ed25519 -C "tom@example.com"ssh-keygen -t rsa -b 4096 -C "tom@example.com"ssh-copy-id -i ~/.ssh/id_ed25519.pub user@hostssh-keygen -lf ~/.ssh/id_ed25519.pubeval $(ssh-agent) && ssh-add ~/.ssh/id_ed25519Config File ~/.ssh/config 6
Host prodHostName 1.2.3.4User deployIdentityFile ~/.ssh/id_ed25519ProxyJump bastionLocalForward 8080 127.0.0.1:80Port Forwarding & Proxy 5
ssh -L 8080:127.0.0.1:80 user@hostssh -R 9090:127.0.0.1:80 user@hostssh -D 1080 user@hostssh -N -L 8080:127.0.0.1:80 user@hostssh -J bastion user@internalParameter matrix
| Parameter | Effect | Example |
|---|---|---|
-i | Private key file to authenticate with. | ssh -i ~/.ssh/id_ed25519 user@host |
-p | Port to connect to; the default is 22. | ssh -p 2222 user@host |
-L | Local port forwarding tunnel. | ssh -L 8080:127.0.0.1:80 user@host |
-R | Remote port forwarding tunnel. | ssh -R 9000:localhost:3000 user@host |
-N | Open the tunnel only, with no remote shell attached. | ssh -N -L 5432:localhost:5432 db@host |
-f | Run the connection in the background. | ssh -fN -L 8080:localhost:80 user@host |
scp -r | Copy a directory tree recursively. | scp -r ./app user@host:/srv/app |
StrictHostKeyChecking | Choose how strictly host fingerprints are verified. | ssh -o StrictHostKeyChecking=no user@host |
ProxyJump | Reach the target through a jump host. | ssh -J jump@bastion user@host |
IdentitiesOnly | Offer only the named key, so a pile of keys cannot get you rejected. | ssh -o IdentitiesOnly=yes -i key user@host |
Common pitfalls
SymptomKey-based login fails with Permission denied (publickey).
CauseThe private key is readable by other users (mode 644), authorized_keys has the wrong ownership, or PubkeyAuthentication is off on the server.
Fixchmod 600 the ~/.ssh/id_* keys and 700 the .ssh directory; on the server confirm PubkeyAuthentication yes and the AuthorizedKeysFile path.
SymptomThe connection hangs for a long time and then times out.
CauseThe network is unreachable, a firewall drops port 22, or UseDNS makes the reverse lookup stall.
FixConfirm reachability with ping or telnet host 22, and set UseDNS no plus GSSAPIAuthentication no on the server to speed up the handshake.
SymptomWARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!
CauseThe host was rebuilt or moved to a new address, so its fingerprint no longer matches known_hosts — which is also exactly what a man in the middle looks like.
FixOnce you have confirmed the change is legitimate, drop the stale record with ssh-keygen -R host and reconnect. If the change is unexpected, verify the host before going any further.
Symptomscp or ssh is refused because the client offers too many keys.
Causessh tries each key in turn until the server hits MaxAuthTries and drops the connection.
FixPass -o IdentitiesOnly=yes with a single -i, or bind an IdentityFile per host in ~/.ssh/config.
SymptomThe tunnel is established but the forwarded port still refuses connections.
CauseThe forward target is not reachable as localhost on the far side, or BindAddress is restricted.
FixRemember that the remote half of -L local:remoteHost:remotePort is resolved on the server itself, and check the GatewayPorts setting.
SymptomDirect root login is refused.
CausePermitRootLogin is set to no, which is the recommended default.
FixLog in as an ordinary user and escalate with sudo. If root really is required, use PermitRootLogin prohibit-password with key authentication only.
Troubleshooting
1Trace the connection attempt in detail
ssh -v user@hostThe verbose output tells you whether the attempt dies at authentication, stalls in the handshake, or is refused outright, which is the stage to look at.
2Check that the port is actually reachable
nc -zv host 22Separates a blocked network path from a service that is simply not listening.
3Drop a stale host key and reconnect
ssh-keygen -R hostRun this only after confirming that the change of host is a legitimate one.
4Verify that passwordless login works
ssh -o BatchMode=yes user@host echo okBatchMode never prompts for a password, so a clean run proves key authentication is genuinely in place.
Command Examples
Set up passwordless login with an ed25519 key in three steps
ssh-keygen -t ed25519 -C "tom@example.com"
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@host
ssh user@hostKeys are generated locally and only the public half is pushed to the server; the private key never leaves your machine. When passwordless login fails, retrace this chain to find which step was skipped.
Output
Generating public/private ed25519 key pair. Your identification has been saved in /home/tom/.ssh/id_ed25519 Number of key(s) added: 1.
Verify the host key fingerprint before trusting a new server
ssh-keygen -lf ~/.ssh/id_ed25519.pubThe first field is the key length and type, the second is the SHA256 fingerprint, and the comment follows it. Compare that fingerprint character by character with the value the server admin gives you, and do not log in if it differs.
Output
256 SHA256:8kQ1OQ0pZ3mLpXv2Rb7nT4cYdE6sA1fH5gJ9uI0oLk tom@example.com (ED25519)
Turn on verbose debugging when authentication fails
ssh -vvv -o ConnectTimeout=5 user@host-vvv prints the negotiated key algorithms, which of your public keys the server accepted, and exactly which authentication step stalled. Adding -o ConnectTimeout=5 prevents an unreachable host from hanging forever.
Output
debug1: Authenticated to host (22).
Reach an internal host through a bastion in one command
ssh -J bastion user@internalEquivalent to writing ProxyJump bastion in ~/.ssh/config, and it removes the two nested sessions (log into the bastion, then ssh again) plus the risk that the inner session lacks your key file.
Keep a tunnel running in the background for remote debugging
ssh -N -L 8080:127.0.0.1:80 user@host-N opens the tunnel without running a remote command, so run it under & or inside tmux. Local port 8080 is then remote port 80, and pointing your frontend at 8080 reaches the remote backend.
Common Pitfalls
- ssh-copy-id appends to authorized_keys and creates the file if missing; running it repeatedly piles up duplicate keys and slows login and audits.
- Forwarding your agent with ssh -A, or copying a private key onto a server, means one leaked key compromises every host it can reach; use ssh-agent plus ssh-add instead.
- If ~/.ssh/config is not mode 600 ssh silently refuses to read it and fails with "Permissions too open" — a confusing message that rarely points at permissions.
- ssh -J differs from nested ssh -t: when the outer tunnel drops, inner sessions die with it and interactive programs receive SIGHUP, so long jobs need nohup or screen.
- Answering yes at the first host-key prompt writes that fingerprint permanently into known_hosts; unless you verified it with the admin first, you have just waived MITM protection.
- Moving off port 22 with ssh -p changes nothing about authentication strength, and every hardcoded port in ssh-copy-id scripts and automation must be updated in lockstep or bulk access breaks.
Tips
- Use ssh-copy-id for passwordless login — manually editing authorized_keys is error-prone with permissions and format.
- ~/.ssh directory must be 700, private keys must be 600 — SSH refuses keys with loose permissions.
- Add -N -f to run port forwarding in the background, but remember to stop with ssh -O exit or kill to avoid lingering processes.
FAQ
I configured key-based login but the server still asks for a password. How do I narrow it down?
Check three things in order. On the server, run sshd -T | grep authorizedkeysfile to confirm the authorized keys path actually matches where you dropped the file — some distros expect .ssh/authorized_keys2 or a different home. Then verify ownership and permissions: .ssh must be 700 and authorized_keys 600, and sshd silently ignores keys that are group- or world-readable. Finally run ssh -vvv user@host on the client and look for an "Offering public key" line. If it never appears, the client never picked up the private key — usually the agent has no key loaded, or IdentityFile in ~/.ssh/config points at the wrong path.
Why is my Host alias in ssh_config being ignored?
Almost always it is indentation. Keywords such as Host, HostName, User and IdentityFile must start at column zero — no leading spaces or tabs — while their values may be indented. Second, ssh uses the first matching Host block, so wildcard aliases have to sit above specific hosts or they will never be reached. Verify with ssh -G alias, which prints the fully resolved configuration, instead of repeatedly attempting connections.
When should I use local forwarding versus a dynamic proxy?
It depends on whether the destination is known. ssh -L 8080:127.0.0.1:80 is local forwarding with a fixed target — ideal for reaching one known remote service, here mapping remote port 80 to local 8080. ssh -D 1080 is dynamic forwarding: it opens a SOCKS5 proxy and the client picks the destination per request, which suits browsing multiple remote sites or temporarily routing all egress through the far end. Adding -N to either turns it into a pure tunnel with no command execution, which is how you run it in the background.
The connection just drops with no error message. How do I find the cause?
Run ssh -vvv and read the last few lines before the drop. If it stops at kex_exchange_identification, the session never reached authentication — usually sshd is down, the port is firewalled, or MaxStartups is rate-limiting you. If it disconnects immediately after auth succeeds, the remote user shell is probably exiting with a non-zero status from a startup script, or a LocalCommand failed. Two targeted probes help: force IPv4 with -o AddressFamily=inet in case name resolution returns an unreachable AAAA record, and add -o ServerAliveInterval=15 to rule out idle timeouts.
Should I use ed25519 or RSA keys?
Use ed25519 for anything new — keys are shorter, handshakes are faster, and modern OpenSSH (7.2+) supports it everywhere. RSA 4096 is worth keeping for two cases: legacy appliances or embedded devices that only speak RSA, and old clients that predate ed25519 support. Be aware that RSA below 3072 bits is rejected outright by the newer default policies shipped with several distros, so if you go RSA, go 4096. When both key types exist, the client tries ed25519 first; use IdentityFile inside a Host block in ~/.ssh/config to pin a specific key.
Official References
Each command links to its official documentation below, so you can verify the latest usage and read deeper.
Maintained by LaoHand
Publicly updated on Jul 21, 2026, continuously proofread against official docs.
Contact Us
Wrong command or description? Send us corrections, business inquiries or product feedback by email.
Contact Us