Connecting
Port 22 is the default. Every option below can also live in the config file so you only type .
| Command | What it does |
|---|---|
| Log in as user on host (a name or an IP) | |
| Log in with your local username | |
| Non-standard port (lowercase p; scp and sftp use uppercase P) | |
| Use a specific private key | |
| Username as a flag instead of user@host | |
| Go through a jump host (bastion) first | |
| Jump host with its own user and port | |
| Cloud instance: the you downloaded and the image's default user | |
| Raspberry Pi on the same network, by mDNS name | |
| Send a keepalive every 60 seconds so an idle session is not dropped | |
| Accept a never-seen host key without a prompt, still refuse a changed one | |
| Give up after 5 seconds instead of the system default | |
| Compress traffic, useful on slow links | |
| Force IPv4 ( forces IPv6) | |
| Scripted password login (separate package; the password lands in history, use a key instead) | |
| Print the client version | |
| Log out (Ctrl+D on an empty prompt does the same) | |
| Kill a hung session; type it at the start of a line ( lists the other escapes) |
If the connection is refused or hangs, check the port from outside with the SSH port checker, then see Connection refused or Could not resolve hostname.
Syntax and Flags
Options go before the host. Anything after the host is treated as the command to run on the far end, so sends to the remote shell instead of turning on verbose output.
ssh [options] [user@]host [command]| Flag | What it does |
|---|---|
| Port on the server (scp and sftp spell it ) | |
| Private key to offer | |
| Username, when you do not write | |
| Jump through a bastion first | |
| Any setting, same names as the config file | |
| Read a different config file | |
| , , | Verbose; repeat for more detail |
| Quiet: suppress warnings and banners | |
| Force a terminal, for sudo and editors ( forbids one) | |
| Do not run anything, just hold the connection open | |
| Drop to the background once authenticated | |
| Take stdin from so ssh does not eat your input | |
| , , | Local, remote, and SOCKS forwarding |
| Forward your agent to the remote host | |
| , | X11 forwarding, untrusted and trusted |
| Compress the connection | |
| , | Force IPv4 or IPv6 |
| Print the effective config for a host and exit | |
| List what this build supports (, ) | |
| Print the version |
Short flags stack, so is the same as . Every name also works as a plain line in , which is where it belongs once you type it twice.
Remote Commands
| Command | What it does |
|---|---|
| Run one command and disconnect | |
| Several commands, quoted as one string | |
| Force a terminal so sudo can ask for a password | |
| Run a local script on the remote host | |
| Save remote output to a local file | |
| Stream a log until Ctrl+C | |
| In double quotes, escape so the remote shell expands it; single quotes need nothing | |
| Start a job that keeps running after you disconnect | |
| Run in the background without ssh grabbing your terminal's input | |
| Never prompt; fail instead (for cron and scripts) | |
| Pass a local variable through (the server needs ) | |
| After ssh returns: the remote command's exit code, or 255 if ssh itself failed |
For anything longer than one line, pipe a heredoc into a remote shell. Quoting the marker as stops your local shell touching the body:
ssh user@host 'bash -s' <<'EOF'
set -euo pipefail
cd /var/www/app
git pull --ff-only
sudo systemctl reload nginx
EOFCopying Files
scp and sftp use the same keys, ports, and config aliases as ssh. Remote paths are written .
| Command | What it does |
|---|---|
| Upload a file | |
| Download a file to the current directory | |
| Copy a directory | |
| Non-standard port (uppercase P) | |
| Use a specific key | |
| Preserve timestamps and permissions | |
| Copy between two servers through your machine | |
| Limit bandwidth to 8000 Kbit/s | |
| Config alias in place of user@host | |
| Interactive session: , , , , , | |
| sftp on a non-standard port (uppercase P again) | |
| Resumable sync for big trees; see the rsync cheat sheet | |
| rsync over a non-standard port |
Keys
| Command | What it does |
|---|---|
| Generate a key pair at (the comment is just a label) | |
| RSA key for old servers that reject ed25519 | |
| Key at a chosen path with no passphrase (automation) | |
| Append your public key to the server's | |
| Specific key and port | |
| Manual ssh-copy-id where it is not installed | |
| Print the public key from a private key | |
| Change or remove the passphrase | |
| Show the fingerprint | |
| Permissions ssh insists on; a world-readable key is ignored | |
| Cloud keys work as-is once locked down |
Generate a key in the browser with the SSH key generator, check a key that refuses to work with the SSH key validator, and for PuTTY convert it with the PEM to PPK converter. If the server still says Permission denied (publickey), it is almost always permissions or the wrong key being offered.
Config File
turns a long command into . It needs . Build one with the SSH config generator.
Host prod
HostName 203.0.113.10
User deploy
Port 2222
IdentityFile ~/.ssh/prod_ed25519
IdentitiesOnly yes
ProxyJump bastion
Host bastion
HostName bastion.example.com
User admin
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
AddKeysToAgent yes| Directive | What it does |
|---|---|
| The alias you type after ; applies to every host | |
| Real address behind the alias | |
| Username | |
| Port | |
| Key to offer | |
| Offer only that key; fixes Too many authentication failures | |
| Jump host, same as | |
| Keepalive; fixes idle drops and Broken pipe | |
| Give up after 3 missed keepalives | |
| Agent forwarding for this host, same as | |
| A tunnel that opens every time you connect | |
| Load a key into the agent the first time it is used | |
| Trust new hosts on first use, still refuse changed keys | |
| Split the file (goes at the top) | |
| Print the settings that apply to a host after all matching blocks merge | |
| Use a different config file |
Port Forwarding
Syntax is , where the destination is named from the SSH server's point of view, so means the server itself.
| Command | What it does |
|---|---|
| Local: your reaches port 80 on host | |
| Local: reach a database that only host can see | |
| Remote: port 9000 on host reaches port 3000 on your machine | |
| SOCKS proxy on local port 1080; point a browser or at it | |
| Tunnel only, no shell | |
| Tunnel in the background | |
| Let other machines on your network use the local forward | |
| Several tunnels, repeat the flag | |
| Stop background tunnels | |
| Close a shared master connection when is on |
For to accept connections from outside the server, must be set in the server's . Confirm the far end is actually listening with the SSH port checker before you blame the tunnel.
Agent
The agent holds decrypted keys so you type the passphrase once per login.
| Command | What it does |
|---|---|
| Start an agent in this shell | |
| Load the default keys, prompting for passphrases | |
| Load a specific key | |
| List loaded keys ( prints the public keys) | |
| Unload one key ( unloads all) | |
| Load a key that expires after an hour | |
| macOS: remember the passphrase in Keychain | |
| Forward the agent so the remote host can use your keys, for on a server | |
| Windows PowerShell (as admin): turn on the built-in agent |
Known Hosts
| Command | What it does |
|---|---|
| Remove a host's old key after a reinstall; fixes Host key verification failed | |
| Same for a non-standard port | |
| Check whether a host is in known_hosts | |
| Pre-trust a host in scripts (compare the fingerprint first) | |
| On the server: print its fingerprint to compare against the warning | |
| Skip the check entirely; throwaway lab machines only | |
| Do not record the host at all (pair with the line above) |
Windows
Windows 10 1809 and later ship the same OpenSSH client, so every command above works unchanged in cmd, PowerShell, and Windows Terminal. What differs is installing it, where it looks for config, and how you lock down a key file.
| Command | What it does |
|---|---|
| PowerShell: is the client installed at all | |
| Install it (admin PowerShell), then open a new terminal | |
| Which you are actually running; Git Bash and WSL each ship their own | |
| Where the Windows client reads config, same syntax as on Linux | |
| PowerShell way to write a key path | |
| Windows stand-in for on a downloaded key | |
| Use the WSL client and its separate from PowerShell |
PowerShell and Git Bash keep separate directories, so a key generated in one is invisible to the other. files from PuTTY do not work with this client at all; convert them with the PPK to PEM converter or use the PuTTY commands sheet instead.
X11 Forwarding
| Command | What it does |
|---|---|
| Forward X11 so GUI programs on the server open on your screen | |
| Trusted X11 with fewer restrictions; try it when gives errors | |
| Quick test |
The server needs in and the package. macOS needs XQuartz, Windows needs VcXsrv or the PuTTY setup in Cannot open display.
Debugging
| Command | What it does |
|---|---|
| Show the handshake, which keys are offered, and where it stops | |
| Maximum detail | |
| Print the effective config for a host | |
| Test a key against GitHub without asking for a shell | |
| Stop the agent from offering every key it holds | |
| List supported key types (, for the others) | |
| Talk to an old server that only offers ssh-rsa | |
| Is the port open at all (or use the SSH port checker) | |
| On the server: watch the login log ( on RHEL and Fedora) | |
| On the server: check syntax before restarting | |
| On the server: apply config changes ( on RHEL and Fedora) |
| Error | Fix |
|---|---|
| ssh permission denied publickey | |
| ssh connection refused | |
| ssh host key verification failed | |
| or | ssh broken pipe |
| ssh too many authentication failures | |
| ssh could not resolve hostname |
Exit Codes
after ssh returns tells you whether ssh failed or your remote command did. In a script, is the cheapest reachability check: 0 means the login works.
| Code | What it means |
|---|---|
| The remote command ran and succeeded | |
| to | Passed straight through from the remote command; ssh worked fine |
| The remote shell could not find the command you asked for | |
| The remote command was killed by signal n ( is Ctrl+C, is a kill or the OOM killer) | |
| ssh itself failed: DNS, refused, timed out, host key, or authentication |
is the only ambiguous one, because a remote command is allowed to return it too. If you get 255 and the command definitely ran, rerun with and check whether the handshake completed before blaming the connection.
Gotchas
- ssh takes for the port, scp and sftp take . Lowercase on scp means preserve timestamps, and the copy quietly goes to port 22.
- There is no password flag. If a script needs one, that is , and the better answer is a key plus .
- A private key readable by anyone else is silently skipped. wants 700, keys and want 600, and the home directory must not be group-writable.
- Single-quote remote commands. In double quotes your local shell expands and before ssh sends anything.
- Closing the terminal kills whatever the session was running. Start long jobs inside tmux or under .
- A background tunnel started with outlives the terminal. finds it when a port is unexpectedly busy.
- On Windows, in PowerShell is OpenSSH and reads , not PuTTY's saved sessions. See Windows for the install check and the problem.
- Options belong before the host. does not change the port, it passes to the remote shell and connects on 22.