What SSH is #
SSH (Secure Shell) is the protocol used to reach another computer over a network safely — for remote operation and file transfer. By encrypting the traffic, it sweeps away the three problems cleartext protocols like Telnet and rlogin had: eavesdropping, tampering, and impersonation. It runs on TCP port 22 by default, and is standardized in RFC 4253.
SSH is fundamental infrastructure for modern development: server operations, Git push, CI/CD deployments. OpenSSH (the OpenBSD-derived OSS implementation) ships with virtually every Linux distribution, macOS, and Windows 10 or later, and is now the de-facto standard.

The three steps of an SSH connection #
An SSH session is built in this order. Once you have these three steps in your head, the rest of the mechanism falls into place.
- Host authentication — the client asks "is the other side really the right server?"
- User authentication — the server asks "is the other side really the right user?"
- Encrypted communication — every byte from here on is encrypted with a shared key both sides derived independently.
1. Host authentication #
The server signs a challenge with its host key, and the client saves the resulting fingerprint into ~/.ssh/known_hosts. From the second connection onwards, anything that doesn't match the saved fingerprint triggers a warning — that is, the client can detect a man-in-the-middle. This is called the TOFU (Trust On First Use) model.
The authenticity of host 'example.com' can't be established.
ED25519 key fingerprint is SHA256:abcd1234...
Are you sure you want to continue connecting (yes/no/[fingerprint])?
There is still a window for a MITM on the very first connection, so in stricter setups the server operator publishes the fingerprint through a separate channel (internal wiki, HTTPS page) so users can verify it up front.
2. User authentication #
The two main methods:
| Method | How it works | Recommendation |
|---|---|---|
| Password | The username and password are sent over the already-encrypted channel | △ — magnet for brute-force attacks |
| Public key | The client signs with a private key, the server verifies with the public key | ◎ — the standard in production |
The usual production stance is to disable password authentication entirely and allow public-key auth only.
3. Encrypted communication #
After user authentication, both sides derive a shared key independently using Diffie–Hellman key exchange and use it with a symmetric cipher such as ChaCha20-Poly1305 or AES-GCM to encrypt all subsequent traffic. A passive eavesdropper cannot recover the key, so the payload is unrecoverable (forward secrecy).
How public-key authentication works #
In public-key auth, the client generates a pair of keys.
- Private key — never leaves the client.
- Public key — copied to the server ahead of time.

Generating a key pair #
ssh-keygen -t ed25519 -C "your@email"
This produces ~/.ssh/id_ed25519 (private) and ~/.ssh/id_ed25519.pub (public). Ed25519 is short, strong, and fast to generate and verify. Pick it by default unless you have a specific compatibility constraint.
Always protect the private key with a passphrase — that way even if the file is stolen, the attacker still can't use it. Pair it with ssh-agent and you only have to enter the passphrase once per login session.
Registering the public key on the server #
ssh-copy-id user@example.com
This appends the public key to the server's ~/.ssh/authorized_keys and sets the right permissions (600).
The authentication flow (challenge / response) #
- The client says "I want to log in with this public key".
- The server sends a random challenge.
- The client signs the challenge with the private key and returns the signature.
- The server verifies the signature with the public key — if it matches, the user is in.
The private key itself never travels across the network. That's the key idea, and it also makes the flow resistant to replay attacks.
Core commands #
# connect
ssh user@example.com
ssh -p 2222 user@example.com # specific port
ssh -i ~/.ssh/special_key user@host # specific key file
ssh user@host 'uptime' # run a one-off command
ssh -vvv user@host # verbose log (troubleshooting)
# key management
ssh-keygen -t ed25519 # new key
ssh-keygen -p -f ~/.ssh/id_ed25519 # change passphrase
ssh-keygen -l -f ~/.ssh/id_ed25519.pub # show fingerprint
ssh-keygen -R example.com # remove an entry from known_hosts (key rotation)
File transfer #
Three file-transfer tools ride on the SSH encrypted channel. Pick whichever fits the task.
# scp — one-shot copy
scp local.txt user@host:/path/
scp -r local-dir/ user@host:/path/
# sftp — interactive file ops
sftp user@host
sftp> get remote.txt
sftp> put local.txt
# rsync over SSH — delta transfer (great for large file sets and backups)
rsync -avz -e ssh src/ user@host:/dst/
Port forwarding (tunneling) #
Port forwarding piggybacks another app's traffic on the SSH encrypted channel. There are three patterns:
| Pattern | Flag | Typical use |
|---|---|---|
| Local forward | -L |
Reach a remote database behind a bastion straight from your laptop |
| Remote forward | -R |
Temporarily expose a local app to the outside (ngrok-style) |
| Dynamic | -D |
SOCKS proxy — push browser traffic out through the SSH endpoint |
Example: connect to a remote PostgreSQL through a bastion with plain psql.
ssh -L 5432:localhost:5432 user@bastion.example.com
# In another terminal
psql -h localhost
Bastion hopping (ProxyJump) #
When you have to hop through several bastions, chain them with -J. It replaces the older ProxyCommand.
ssh -J user@bastion user@internal-server
ssh -J user@b1,user@b2 user@target # multi-hop
Minimum server-side settings (sshd_config) #
In /etc/ssh/sshd_config, these three lines are the absolute minimum to set.
PermitRootLogin no # forbid direct root login
PasswordAuthentication no # disable passwords (public keys only)
PubkeyAuthentication yes # enable public-key authentication
After editing, run sshd -t to validate the syntax, then systemctl reload sshd to apply. Do not close your current session — test the change from a second terminal first, and only close the original session once the new one works. That's the safety net that keeps you from locking yourself out.
Handy client-side config (~/.ssh/config) #
Give each destination a short alias and ssh internal is all you need.
Host bastion
HostName bastion.example.com
User admin
Port 2222
IdentityFile ~/.ssh/id_ed25519_admin
Host internal
HostName 10.0.0.5
User deploy
IdentityFile ~/.ssh/id_ed25519_deploy
ProxyJump bastion
References #
- Zukai Nyumon TCP/IP 2nd Edition — Visual TCP/IP, section 6-5-2 SSH (Management Access Protocols)
- OpenSSH Documentation
- RFC 4253 — The Secure Shell (SSH) Transport Layer Protocol