SSH (Secure Shell) is how you talk to a remote machine without someone in the middle being able to read or tamper with the conversation. It's encrypted, authenticated, and runs over TCP port 22. Most of the time you're using it to get a shell on a server — but it also does port forwarding, file transfers, and tunneling through firewalls.
Password auth is simple: you type a password, the server checks it. Public key auth is better.
You generate a key pair — a private key you keep and a public key you share. The server stores your public key in ~/.ssh/authorized_keys. When you connect:
Your private key never leaves your machine. That's the point.
ssh-keygen -t ed25519 -C "your@email.com"
| Flag | What it does |
|---|---|
-t |
Key type |
-C |
Comment (shows up in authorized_keys) |
-f |
Output filename |
-b |
Key size (RSA only) |
Ed25519 is the default choice — small, fast, and secure. Use RSA 4096 only if you need to interop with something old.
Keys land in ~/.ssh/ by default:
| File | What it is |
|---|---|
id_ed25519 |
Private key — never share this |
id_ed25519.pub |
Public key — safe to share |
authorized_keys |
Public keys allowed to log in (server-side) |
known_hosts |
Fingerprints of servers you've connected to |
config |
Per-host connection settings |
Add your public key to a server:
ssh-copy-id user@host # copies ~/.ssh/id_*.pub
ssh-copy-id -i ~/.ssh/key.pub user@host # specific key
Or manually append the public key to ~/.ssh/authorized_keys on the server.
~/.ssh/config is one of the most underused tools in SSH. It lets you define aliases and defaults so you're not typing out flags every connection.
Host myserver
HostName 203.0.113.42
User deploy
Port 2222
IdentityFile ~/.ssh/id_ed25519
Host bastion
HostName bastion.example.com
User admin
Host internal
HostName 10.0.1.5
User ubuntu
ProxyJump bastion
With that config, ssh myserver is all you need.
| Directive | What it does |
|---|---|
HostName |
Actual address or IP |
User |
Login username |
Port |
Port (default 22) |
IdentityFile |
Which private key to use |
ProxyJump |
Hop through another host first |
ForwardAgent |
Forward your local SSH agent through the connection |
ServerAliveInterval |
Keepalive ping interval in seconds |
StrictHostKeyChecking |
Whether to reject unknown hosts |
Host * at the top acts as a fallback for any host not matched above.
The agent holds your decrypted private keys in memory so you don't retype your passphrase on every connection.
eval "$(ssh-agent -s)" # start the agent
ssh-add ~/.ssh/id_ed25519 # add a key
ssh-add -l # list loaded keys
ssh-add -d ~/.ssh/id_ed25519 # remove a key
macOS Keychain integrates with ssh-agent automatically. Add to your config to persist keys across reboots:
Host *
AddKeysToAgent yes
UseKeychain yes
Agent forwarding (ForwardAgent yes or -A flag) lets a remote server use your local agent to make further SSH connections — useful when jumping through a bastion. Be careful: a compromised remote host can use your agent while it's forwarded.
# Basic connection
ssh user@host
ssh -p 2222 user@host # custom port
# Run a command without opening a shell
ssh user@host "df -h"
# Copy files
scp file.txt user@host:~/ # local → remote
scp user@host:~/file.txt . # remote → local
scp -r dir/ user@host:~/ # recursive
# rsync (better for large/incremental transfers)
rsync -avz dir/ user@host:~/dir/
SSH can forward TCP traffic through the encrypted tunnel.
Local forwarding — access a remote service as if it were local:
ssh -L 8080:localhost:3000 user@host
# http://localhost:8080 now hits the remote's port 3000
Remote forwarding — expose a local service on the remote server:
ssh -R 9090:localhost:3000 user@host
# remote's port 9090 now hits your local port 3000
Dynamic forwarding — SOCKS5 proxy through the remote:
ssh -D 1080 user@host
# route traffic through the proxy at localhost:1080
| Flag | Type | Direction |
|---|---|---|
-L local:remote |
Local | Your machine → remote |
-R remote:local |
Remote | Remote machine → you |
-D port |
Dynamic | SOCKS5 proxy |
-N |
— | No command, just forward |
-f |
— | Background the process |
Default SSH config is fine for getting started, terrible for production. Edit /etc/ssh/sshd_config:
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
AllowUsers deploy ubuntu
Port 2222
MaxAuthTries 3
Restart after changes: sudo systemctl restart sshd
fail2ban bans IPs after repeated failed attempts. Worth running on anything internet-facing.
First time you connect to a host, SSH shows its fingerprint and asks you to verify:
The authenticity of host '203.0.113.42' can't be established.
ED25519 key fingerprint is SHA256:abc123...
Are you sure you want to continue connecting?
Say yes and it's added to ~/.ssh/known_hosts. If the fingerprint changes on a future connection (server reinstalled, or someone is doing a MITM), SSH refuses with a loud warning. Check it:
ssh-keyscan host 2>/dev/null | ssh-keygen -lf - # get host fingerprint
ssh-keygen -R host # remove old entry from known_hosts