Linux systems 20 entries 13 run in the sandbox

SSH cheat sheet

Keys, agents, config, tunnels and fixing permission denied. Each entry has a worked example and the flags worth knowing, and where one is easy to get wrong, the gotcha.

Keys

ssh-keygen -t ed25519 -C <comment>

Generate a modern key pair. The .pub half goes on servers; the private half never leaves your machine.

ssh-keygen -t ed25519 -C "alice@laptop" -f ~/.ssh/id_alice
-t ed25519
Key type; ed25519 is short, fast and the default choice today.
-C TEXT
Comment stored in the public key, usually who and where.
-f PATH
Output file, for example ~/.ssh/deploy_ed25519.
-N ''
Empty passphrase; only for automation keys kept in a secret store.

ssh-copy-id -i <key.pub> <user>@<host>

Append your public key to the remote authorized_keys with the correct permissions.

ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@10.0.0.12
-i FILE
Which public key to install.
-p PORT
Remote SSH port.

chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys

Set the permissions sshd insists on. With StrictModes it silently ignores keys in files others can write.

~/.ssh 700
Only the owner may enter or list it.
authorized_keys 600
Only the owner may read or write it.
private keys 600
ssh refuses keys readable by others: 'UNPROTECTED PRIVATE KEY FILE'.
home not g+w
A group-writable home directory also disables key auth.

Gotcha The files must also be owned by the account. A root-owned authorized_keys created with sudo fails just like a wrong mode.

ls -la ~/.ssh

Check owners and modes of everything sshd reads for a user.

ssh-keygen -lf <key>

Print a key's fingerprint so you can match it against what the server logged or what a teammate sent.

ssh-keygen -lf ~/.ssh/id_ed25519.pub
256 SHA256:x1Nf0t9mB3Yq0eR7kUq3xJ7l5dVnQn2cQ8m5s2mYw9A alice@laptop (ED25519)
-l
Show the fingerprint.
-f FILE
Key file to read.
-E md5
Legacy MD5 format for old tooling.

ssh-keygen -R <host>

Remove a stale host key after a server was rebuilt, so ssh can learn the new one.

ssh-keygen -R web-01.internal
-R HOST
Delete every key for HOST from known_hosts.
-F HOST
Show the stored key for HOST.

Gotcha 'REMOTE HOST IDENTIFICATION HAS CHANGED' can also mean an attack. Confirm the host was rebuilt before removing the key.

ssh-keyscan <host> >> ~/.ssh/known_hosts

Collect a host's public keys, for example to pre-seed known_hosts in a CI job instead of disabling host key checks.

ssh-keyscan -t ed25519 github.com >> ~/.ssh/known_hosts
-t TYPE
Key types to fetch, such as ed25519 or rsa.
-H
Hash host names in the output.

Gotcha keyscan trusts whatever answers on the network. Compare the fingerprint with the provider's published value before relying on it.

from=...,command=... <key>

Restrict an automation key in authorized_keys to one source network and one forced command.

from="10.0.0.0/8",command="/usr/local/bin/backup",no-pty,no-port-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... backup@ci
from=
Allowed client addresses or CIDRs.
command=
Always run this command, whatever the client asked for.
no-pty
No interactive terminal.
no-port-forwarding
Block tunnels through this key.

Connect & debug

ssh -v <user>@<host>

Connect with verbose client logging to see which keys were offered and why authentication failed.

ssh -v deploy@10.0.0.12
debug1: Offering public key: /home/alice/.ssh/id_ed25519 ED25519 SHA256:x1Nf0t...
debug1: Authentications that can continue: publickey
deploy@10.0.0.12: Permission denied (publickey).
-v / -vvv
Increasing verbosity.
-i KEY
Use a specific identity file.
-p PORT
Connect to a non-default port.
-o IdentitiesOnly=yes
Offer only the given key, avoiding 'Too many authentication failures'.

Gotcha Client logs show what you offered; the reason it was rejected is in the server's auth log or journalctl -u ssh.

journalctl -u ssh

Read the server side of a failed login: bad ownership, wrong user, disallowed key type.

journalctl -u ssh -n 50
-u ssh
Debian and Ubuntu unit name.
-u sshd
RHEL, Fedora and Amazon Linux unit name.

Gotcha 'Authentication refused: bad ownership or modes' names the exact file or directory to fix.

ssh <host> '<command>'

Run a single command remotely and return its output and exit code.

ssh web-01 'systemctl is-active nginx'
-t
Force a TTY, needed for sudo password prompts or top.
-o BatchMode=yes
Fail instead of prompting; right for scripts.
-o ConnectTimeout=5
Give up quickly on unreachable hosts.

ssh-add <key>

Load a key into the agent once so you are not asked for the passphrase on every connection.

eval "$(ssh-agent -s)" && ssh-add ~/.ssh/id_ed25519
ssh-add -l
List loaded keys.
ssh-add -t 8h
Expire the key from the agent after eight hours.
-D
Remove every loaded key.

Gotcha Agent forwarding (-A) lets root on the remote host use your keys while you are connected. Prefer ProxyJump.

~/.ssh/config

Name hosts once and stop retyping users, keys, ports and bastions.

Host bastion
    HostName bastion.example.com
    User alice
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes

Host app-*
    User deploy
    ProxyJump bastion
    ServerAliveInterval 30
Host
Alias or glob that the following lines apply to.
HostName
The real DNS name or IP address.
IdentitiesOnly yes
Use only the configured key.
ProxyJump
Hop through a bastion transparently.
ServerAliveInterval
Keepalives so idle sessions survive NAT timeouts.

Gotcha The first matching value wins, so put specific Host blocks above wildcards like Host *.

Tunnels & copies

ssh -J <bastion> <target>

Reach a private host through a bastion without copying keys onto the bastion.

ssh -J alice@bastion.example.com deploy@app-01.internal
-J HOST[,HOST]
One or more jump hosts, in order.

ssh -L <lport>:<host>:<rport> <bastion>

Forward a local port to a service only the remote side can reach, such as a private database.

ssh -N -L 5432:db.internal:5432 bastion
-L L:H:R
localhost:L connects to H:R as seen from the SSH server.
-N
No remote command; only forward.
-f
Go to the background after authenticating.

ssh -R <rport>:localhost:<lport> <host>

Expose a local port on the remote host, for example to let a remote box reach a service on your laptop.

ssh -N -R 9000:localhost:3000 dev-box
-R R:H:L
remote:R connects to H:L on your side.
GatewayPorts
sshd setting that controls binding beyond localhost.

ssh -D <port> <host>

Start a local SOCKS proxy that routes traffic through the remote host.

ssh -N -D 1080 bastion
-D PORT
Dynamic forwarding on localhost:PORT.

scp <src> <user>@<host>:<dest>

Copy files over SSH. For large or repeated copies, rsync is faster and resumable.

scp -r ./dist deploy@web-01:/tmp/release-42
-r
Recursive.
-P PORT
Port; note the capital P, unlike ssh -p.
-J HOST
Copy through a jump host.

Harden the server

sshd_config hardening

Baseline settings for /etc/ssh/sshd_config.d/10-hardening.conf.

PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AllowGroups ssh-users
MaxAuthTries 3
LoginGraceTime 30
PermitRootLogin no
Log in as a named user, then sudo.
PasswordAuthentication no
Keys only; brute force becomes pointless.
AllowGroups
Only members of these groups may log in.
MaxAuthTries
Disconnect after this many failures.

Gotcha Keep your current session open and test a new login from a second terminal before you log out.

sshd -t

Validate the sshd configuration before reloading. A broken config plus a restart can lock everyone out.

sudo sshd -t && sudo systemctl reload ssh
-t
Test mode: check syntax and keys, then exit.
-T
Print the effective configuration.

Your pager is ready.

Free, instant, and it works on your phone. No signup: start as a guest and save your progress later.