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.