SSH Cheat Sheet
Connect, authenticate with keys, configure ~/.ssh/config, transfer files, tunnel ports, and harden sshd safely.
A quick reference for ssh — connecting to remote servers, managing keys, moving files, tunneling, and locking down the server side without locking yourself out.
Basic connection
ssh user@host # connect as "user" to "host" on the default port (22)
ssh -p 2222 user@host # connect using a non-default port
-p selects a non-default port. Without a username, ssh defaults to your current local username.
Key-based authentication
ssh-keygen -t ed25519 -C "you@example.com" # generate a new keypair, comment is just a label
ssh-copy-id user@host # copy your public key into the remote authorized_keys
ed25519 is the modern default — smaller and faster than RSA, with no known weaknesses at reasonable key sizes. ssh-copy-id appends your public key to the remote ~/.ssh/authorized_keys for you, over a normal password login.
chmod 700 ~/.ssh # only you can access this directory at all
chmod 600 ~/.ssh/id_ed25519 # private key: owner read/write only
chmod 644 ~/.ssh/id_ed25519.pub # public key: fine to be world-readable
SSH silently refuses to use a private key if its permissions are too open.
600(owner read/write only) is required, and700on~/.sshitself keeps other users out entirely.
~/.ssh/config — shortcuts for hosts you use often
Host prod # alias you'll type instead of the details below
HostName 203.0.113.10 # actual IP or hostname to connect to
User deploy # username to log in as
Port 2222 # non-default SSH port
IdentityFile ~/.ssh/id_ed25519 # which private key to use for this host
ssh prod # uses every setting above automatically
Once defined, ssh prod (or scp file prod:/path) uses all of those settings automatically — no more re-typing IP, user, and port every time.
Copying files
scp file.tgz user@host:/tmp/ # copy a local file to a remote path
scp -P 2222 user@host:/var/log/app.log ./ # copy a remote file to the current directory
scp -r ./dist/ user@host:/var/www/app/ # copy a directory recursively
-P (capital) sets the port for scp — note it’s different from ssh’s lowercase -p. For anything recurring or resumable, prefer rsync -e ssh instead.
sftp user@host # open an interactive file-transfer session
An interactive alternative to scp, with get/put/ls/cd commands — useful for browsing before transferring.
Running a remote command without a full session
ssh user@host 'df -h && uptime' # run a command remotely, print its output locally, then disconnect
Runs the command on the remote host and returns, without dropping you into an interactive shell — handy in scripts.
Port forwarding (tunneling)
ssh -L 8080:localhost:80 user@host # local forward: reach the remote's port 80 via localhost:8080
Local forward: makes the remote’s port 80 reachable at localhost:8080 on your machine — e.g. reaching an internal admin panel that only listens on the server itself.
ssh -R 9000:localhost:3000 user@host # remote forward: expose local port 3000 on the remote's localhost:9000
Remote forward: makes YOUR local port 3000 reachable from the remote host at its localhost:9000 — e.g. temporarily exposing a local dev server to a remote box.
ssh -D 1080 user@host # dynamic forward: turn this connection into a SOCKS proxy on port 1080
Dynamic forward: turns the SSH connection into a SOCKS proxy on local port 1080 — route a browser’s traffic through the remote server.
Jumping through a bastion host
ssh -J bastion user@internal-host # hop through "bastion" to reach internal-host in one command
Chains through bastion to reach internal-host in one command, instead of manually SSH-ing into the bastion first and then again from there. Works well combined with ~/.ssh/config host aliases.
Keeping the connection alive
Host *
ServerAliveInterval 60 # send a keepalive packet every 60 seconds
ServerAliveCountMax 3 # give up after 3 missed replies
Add to ~/.ssh/config to stop idle connections from being silently dropped by NAT devices or firewalls — a keepalive packet every 60s, tolerating 3 missed replies before giving up.
Server-side hardening (/etc/ssh/sshd_config)
sudo nano /etc/ssh/sshd_config # edit the server-side SSH daemon config
PermitRootLogin no # disable direct root login over SSH
PasswordAuthentication no # require key-based login only
Port 2222 # listen on a non-default port
After editing, always validate before restarting:
sudo sshd -t # validate the config syntax before restarting
sudo systemctl restart sshd # apply the new config
Do this in the right order or you can lock yourself out permanently: confirm key-based login works for your account first, keep your current session open, test a new connection in a second terminal after each change, and only disable
PasswordAuthenticationonce you’ve confirmed key login succeeds.sshd -tcatches syntax errors before they reach a running config.
Troubleshooting
ssh -v user@host # verbose output - shows each step of the connection attempt
-v (repeatable up to -vvv) prints what the client is trying and where it fails — the first thing to reach for when a connection behaves unexpectedly.
ssh-keygen -R host # remove a stale/changed entry for "host" from known_hosts
Removes a stale entry from ~/.ssh/known_hosts — needed after a legitimate host key change (e.g. the server was rebuilt), which otherwise shows as a scary “REMOTE HOST IDENTIFICATION HAS CHANGED” warning. Confirm the change is expected before removing it — that warning is also exactly what a machine-in-the-middle attack looks like.
Safety notes
- Never disable
PasswordAuthenticationbefore verifying key-based login actually works — test in a second, still-open session. - Keep private keys
600and never share or commit them; if one might be compromised, revoke it (remove fromauthorized_keys) and generate a new pair. - Prefer
PermitRootLogin noand a regular user withsudo, rather than logging in as root directly. - A changed host-key warning is not always an attack, but never dismiss it without confirming why the server’s key actually changed.