SSH Tips: Keys, Config File, Port Forwarding, rsync and More
On this page
If you manage even one server, you use SSH every day. Most people stop at ssh user@host and typing a password. A few minutes of setup gives you password-free logins, short host names, secure tunnels to remote databases, and connections that do not freeze when you step away.
All examples use OpenSSH, the client that ships with Linux, macOS and current versions of Windows.
Create an SSH key
Key-based login is both safer and more convenient than passwords. Create a key pair with the Ed25519 algorithm, which is fast, short and secure:
$ ssh-keygen -t ed25519 -C "alice@laptop"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/alice/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Press Enter to accept the default location, and choose a passphrase. The passphrase encrypts the private key on disk, so a stolen laptop or backup does not mean a stolen login. The -C comment is just a label to help you recognize the key later.
You now have two files:
~/.ssh/id_ed25519: the private key. Never copy it to servers or share it.~/.ssh/id_ed25519.pub: the public key. This is what you put on servers.
Only very old servers lack Ed25519 support; for those, ssh-keygen -t rsa -b 4096 is the fallback. To add or change the passphrase of an existing key, run ssh-keygen -p -f ~/.ssh/id_ed25519.
Install the key on a server
ssh-copy-id appends your public key to ~/.ssh/authorized_keys on the server and fixes the permissions. You log in with your password one last time:
$ ssh-copy-id -i ~/.ssh/id_ed25519.pub [email protected]
If ssh-copy-id is not available, do the same by hand:
$ cat ~/.ssh/id_ed25519.pub | ssh [email protected] 'mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys'
SSH is strict about permissions. If key login silently falls back to a password, check that ~/.ssh is 700 and authorized_keys and private keys are 600 on the relevant machine, and that your home directory is not writable by others. The permissions guide explains these numbers.
Type your passphrase once: ssh-agent
An agent holds your unlocked key in memory, so you enter the passphrase once per session instead of on every connection. Most desktops start one automatically. On a server or a bare terminal:
$ eval "$(ssh-agent -s)"
$ ssh-add ~/.ssh/id_ed25519
$ ssh-add -l # list loaded keys
The ~/.ssh/config file
Instead of typing ssh -p 2222 [email protected], give each server a short name in ~/.ssh/config:
Host web01
HostName 203.0.113.10
User deploy
Port 2222
IdentityFile ~/.ssh/id_ed25519
Host db
HostName 10.0.0.5
User admin
ProxyJump web01
Host *.internal.example.com
User alice
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
Now ssh web01 is enough, and the same short name works with scp, rsync, git and anything else that uses SSH. A few rules to know:
- For each setting, the first value found wins. Put specific
Hostblocks at the top and the generalHost *block at the end. Hostpatterns can use*and?, and list several names separated by spaces.- Create the file with permissions
600:chmod 600 ~/.ssh/config.
ssh -G web01. It prints the final configuration after all matching blocks are applied, which makes config mistakes easy to spot.Port forwarding
SSH can carry other network traffic through its encrypted connection. Add -N when you only want the tunnel and no remote shell.
Local forwarding (-L): reach a remote service from your machine
A database on a server usually listens only on that server's localhost, for good reasons. To use it from your laptop:
$ ssh -N -L 3307:127.0.0.1:3306 web01
While that runs, connecting to 127.0.0.1:3307 on your laptop reaches port 3306 on web01. The format is -L local_port:destination:destination_port, and the destination is resolved from the server's point of view, so 127.0.0.1 there means the server itself. The same trick opens an admin web page that is bound to localhost: ssh -N -L 8080:127.0.0.1:80 web01, then browse to http://localhost:8080.
Remote forwarding (-R): share a local service with the server
$ ssh -N -R 9000:127.0.0.1:3000 web01
This is the reverse: port 9000 on web01 now leads to port 3000 on your machine, for example a development server you want to test from the server. By default the remote port listens only on the server's localhost.
Dynamic forwarding (-D): a SOCKS proxy
$ ssh -N -D 1080 web01
This starts a SOCKS5 proxy on your machine at localhost:1080. Point a browser at it and its traffic leaves from your server, which is useful on untrusted Wi-Fi or for checking how a site looks from your server's location.
Tunnels you use often can go in the config file with LocalForward 3307 127.0.0.1:3306, RemoteForward and DynamicForward.
Jump hosts with ProxyJump
Many networks expose one "bastion" host and keep everything else private. -J connects through the bastion in a single command, and your key never needs to be copied onto it:
$ ssh -J [email protected] [email protected]
In the config file, ProxyJump does the same, as the db entry above shows: ssh db goes through web01 automatically. Separate several hops with commas.
Copying files: scp vs rsync
scp is simple and works like cp:
$ scp report.pdf web01:/tmp/
$ scp -r web01:/var/log/nginx ./nginx-logs
$ scp -P 2222 file.txt [email protected]:~/ # note: capital -P for the port
rsync is better for anything big or repeated. It copies only what changed, can resume, preserves permissions and times, and shows progress:
$ rsync -avz --progress ./site/ web01:/var/www/site/
$ rsync -avz -e 'ssh -p 2222' ./site/ [email protected]:/var/www/site/
$ rsync -avzn ./site/ web01:/var/www/site/ # -n: dry run, show what would change
-a (archive) keeps permissions, times and symlinks and recurses into directories, -v lists files, and -z compresses in transit. The trailing slash on the source matters: ./site/ copies the contents of site, while ./site creates a site directory inside the destination. rsync must be installed on both ends.
rsync --delete removes files on the destination that are not in the source. Combined with a wrong path or a missing trailing slash, it can wipe a directory. Always run it once with -n first and read the list.Keeping connections alive
Idle SSH sessions often freeze or drop because a router or firewall forgets the connection. ServerAliveInterval 60 in your config makes the client send a small message every 60 seconds, and ServerAliveCountMax 3 gives up after three unanswered ones, so a dead connection ends cleanly instead of hanging.
When a session does hang, you do not need to close the terminal. Press Enter, then type ~. (tilde, period) to make the client disconnect. ~? lists the other escape commands.
If you open many connections to the same server, connection sharing makes every new ssh, scp or git command start almost instantly:
Host *
ControlMaster auto
ControlPath ~/.ssh/cm-%r@%h:%p
ControlPersist 10m
Sensible client settings
A few settings make your own SSH client safer. Add them to the Host * block at the end of ~/.ssh/config:
Host *
IdentitiesOnly yes
ForwardAgent no
HashKnownHosts yes
AddKeysToAgent yes
IdentitiesOnly yesoffers only the keys you configure, instead of every key in your agent, which avoids "too many authentication failures" errors and avoids revealing unrelated keys to servers.ForwardAgent nois the default; keep it, and enable it only for specific hosts you fully trust. Anyone with root on a server you forward to can use your agent while you are connected.ProxyJumpusually removes the need for forwarding.HashKnownHosts yesstores host names in~/.ssh/known_hostsin hashed form, so the file does not list every server you use.AddKeysToAgent yesadds a key to the running agent the first time you unlock it.
Pay attention to host key warnings. The first time you connect, SSH shows the server's key fingerprint; compare it with the one your hosting provider or administrator gives you. If SSH later warns that the host key has changed, find out why before continuing. When you know the change is legitimate (for example, you reinstalled the server), remove the old entry with ssh-keygen -R web01. Do not set StrictHostKeyChecking no to make the warning go away.
When something does not work, ssh -v web01 shows each step of the connection, including which keys are offered and why authentication fails. Every option is documented in ssh(1) and ssh_config(5).
Quick reference
| Task | Command |
|---|---|
| Create a key | ssh-keygen -t ed25519 -C "label" |
| Install key on server | ssh-copy-id user@host |
| Load key into agent | ssh-add ~/.ssh/id_ed25519 |
| Show effective config | ssh -G host |
| Local tunnel | ssh -N -L 3307:127.0.0.1:3306 host |
| Remote tunnel | ssh -N -R 9000:127.0.0.1:3000 host |
| SOCKS proxy | ssh -N -D 1080 host |
| Through a bastion | ssh -J bastion target |
| Sync a directory | rsync -avz src/ host:dest/ |
| Kill a frozen session | Enter then ~. |
| Forget an old host key | ssh-keygen -R host |
| Debug a connection | ssh -v host |
To run commands on many servers in a loop, see the note about ssh -n in the loops guide.