How SSH Works

SSH (Secure Shell) is the protocol that underpins nearly all remote server administration on the internet. Every time a developer pushes code via Git, an engineer logs into a production server, or a CI pipeline deploys an application, SSH is doing the heavy lifting. It provides encrypted communication, strong authentication, and a flexible channel multiplexing system — all over a single TCP connection. Understanding how SSH works at each layer reveals why it has been the dominant remote access protocol for nearly three decades.

The Three Layers of SSH

SSH is not a monolithic protocol. RFC 4251 defines it as three distinct layers, each with its own responsibility. They stack on top of each other: the transport layer provides encryption, the authentication layer verifies identity, and the connection layer multiplexes logical channels over the single encrypted pipe.

Connection Layer (RFC 4254) Channels, port forwarding, shell sessions, SFTP User Authentication Layer (RFC 4252) Public key, password, keyboard-interactive, certificates Transport Layer (RFC 4253) Key exchange, encryption, MAC, compression TCP (port 22)

Transport Layer

The transport layer is the foundation. It runs over a TCP connection (typically port 22) and is responsible for initial key exchange, server authentication, encryption, integrity protection, and optional compression. Once the transport layer completes its handshake, all subsequent data — including the user's password or public key proof — travels inside an encrypted tunnel. This is analogous to how TLS protects HTTPS traffic, but SSH predates TLS and uses its own handshake protocol rather than X.509 certificates.

User Authentication Layer

After the transport layer establishes encryption, the user authentication layer takes over. The client identifies itself (username) and proves its identity through one of several methods: a password, a public key signature, a certificate, or a FIDO2 hardware token. The server decides which methods it accepts. If authentication succeeds, the connection advances to the connection layer.

Connection Layer

The connection layer multiplexes the single encrypted TCP connection into multiple logical channels. Each channel can carry a different stream: an interactive shell, a file transfer, a forwarded TCP port, or an agent connection. This multiplexing is what makes SSH so versatile — a single connection can simultaneously serve a terminal session, tunnel database traffic, and forward an X11 display.

Key Exchange: Establishing the Shared Secret

Before any encrypted data can flow, the client and server must agree on a shared secret without transmitting it over the wire. SSH uses variants of the Diffie-Hellman (DH) key exchange to accomplish this. The math ensures that even an eavesdropper who captures every packet cannot derive the shared secret.

Client Server SSH-2.0-OpenSSH_9.9 (version string) SSH-2.0-OpenSSH_9.9 KEXINIT (algorithm negotiation) Agree: curve25519-sha256, aes256-gcm, hmac-sha2-256 KEX_ECDH_INIT (client public key) KEX_ECDH_REPLY (server public key + host key + signature) NEWKEYS — encrypted channel established All further communication is encrypted

The handshake proceeds as follows:

  1. Version exchange — Client and server send their SSH protocol version strings (e.g., SSH-2.0-OpenSSH_9.9). This is plaintext.
  2. Algorithm negotiation (KEXINIT) — Each side sends a list of supported algorithms for key exchange, encryption, MAC, and compression. They agree on the first algorithm each side supports in the client's preference order.
  3. Diffie-Hellman exchange — The client generates an ephemeral key pair and sends its public value. The server does the same, computes the shared secret, signs the exchange hash with its host key, and sends back its public value along with the host key and signature.
  4. NEWKEYS — Both sides derive session keys from the shared secret and switch to encrypted communication.

Curve25519 and Modern Key Exchange

Modern OpenSSH defaults to curve25519-sha256, which uses Elliptic Curve Diffie-Hellman (ECDH) over Daniel Bernstein's Curve25519. This provides 128 bits of security with a 256-bit key, is resistant to timing attacks by design, and is substantially faster than traditional DH with large prime groups. The older diffie-hellman-group14-sha256 (2048-bit prime) remains as a fallback for compatibility.

Each key exchange produces ephemeral keys — a fresh key pair is generated for every session (and periodically rekeyed during long sessions). This provides forward secrecy: even if the server's host key is later compromised, past session traffic cannot be decrypted. This is the same property that TLS 1.3 achieves with its mandatory ephemeral key exchange.

Post-Quantum Considerations

A sufficiently powerful quantum computer could break both Curve25519 ECDH and traditional DH key exchanges using Shor's algorithm. OpenSSH 9.0 (April 2022) made [email protected] the default key exchange — a hybrid scheme that combines the Streamlined NTRU Prime post-quantum algorithm with Curve25519. This means that even if NTRU is broken by classical methods, Curve25519 still protects the session, and vice versa. For a deeper look at the cryptographic landscape, see our post-quantum cryptography guide.

Host Key Verification and the TOFU Model

During the key exchange, the server proves its identity by signing the exchange hash with its host key — a long-lived key pair that uniquely identifies the server. But how does the client know it is talking to the right server in the first place?

SSH uses a model called Trust On First Use (TOFU). The first time you connect to a server, SSH displays the host key fingerprint and asks you to accept it:

The authenticity of host 'example.com (93.184.216.34)' can't be established.
ED25519 key fingerprint is SHA256:AbCdEf1234567890AbCdEf1234567890AbCdEfGh.
Are you sure you want to continue connecting (yes/no/[fingerprint])?

If you accept, the fingerprint is saved in ~/.ssh/known_hosts. On subsequent connections, SSH compares the server's host key against the stored fingerprint. If they do not match, SSH refuses the connection with a loud warning — this is the WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! message that indicates either a legitimate server change or a potential man-in-the-middle attack.

TOFU is a pragmatic compromise. Unlike TLS/HTTPS, where a Certificate Authority (CA) vouches for a server's identity before you ever connect, SSH trusts the first connection and then verifies consistency afterward. This avoids the complexity of a global PKI but means the first connection is vulnerable if an attacker is already in position. SSH certificates (discussed below) address this limitation.

Authentication Methods

After the encrypted channel is established and the server's identity is verified, the client must authenticate itself. SSH supports multiple methods, negotiated in order of preference.

Password Authentication

The simplest method: the client sends the user's password over the encrypted channel. The server checks it against its local authentication system (PAM, /etc/shadow, LDAP, etc.). While the password is protected by encryption in transit, password authentication is vulnerable to brute-force attacks and credential stuffing. Most security guides recommend disabling it entirely.

Public Key Authentication

The standard method for automated and interactive access. The user generates a key pair (private key stays on the client, public key is installed on the server in ~/.ssh/authorized_keys). During authentication:

  1. The client tells the server which public key it wants to use.
  2. The server checks if that public key is in the user's authorized_keys file.
  3. If found, the server sends a challenge — a random session-specific value.
  4. The client signs the challenge with its private key and sends the signature back.
  5. The server verifies the signature against the public key. If valid, access is granted.

The private key never leaves the client machine. Common key types include:

Certificate-Based Authentication

SSH certificates (not to be confused with X.509/TLS certificates) solve the key distribution problem at scale. Instead of copying public keys to every server's authorized_keys file, a trusted Certificate Authority (CA) signs user keys. The server only needs to trust the CA's public key, and any user certificate signed by that CA is accepted. This is covered in detail in the SSH Certificates section below.

FIDO2/U2F Hardware Keys

OpenSSH 8.2 (February 2020) added support for FIDO2/U2F security keys (YubiKey, SoloKey, Google Titan, etc.) as a second factor or primary authentication method. The key types are [email protected] and [email protected] (the sk- prefix stands for "security key").

With FIDO2 authentication, the private key is generated on and never leaves the hardware token. Authentication requires a physical touch of the device, providing strong protection against remote key theft and malware. The -O resident flag can store the key handle on the token itself, making it portable across machines without needing to carry a separate private key file.

Keyboard-Interactive and Multi-Factor

The keyboard-interactive method supports arbitrary challenge-response dialogs, which PAM modules use to implement TOTP (time-based one-time passwords from apps like Google Authenticator), Duo push notifications, and other multi-factor schemes. SSH also supports requiring multiple methods sequentially via AuthenticationMethods in sshd_config — for example, requiring both a public key and a TOTP code: AuthenticationMethods publickey,keyboard-interactive.

Channel Multiplexing

Once authenticated, the SSH connection layer manages multiple channels over the single encrypted TCP connection. Each channel has a type, a pair of channel numbers (local and remote), and independent flow control with a sliding window mechanism.

Encrypted SSH Connection (single TCP socket) Channel 0 session — interactive shell (bash) Channel 1 session — SFTP subsystem Channel 2 direct-tcpip — forwarded port (localhost:5432) Channel 3 [email protected] — forwarded SSH agent

Channel types include:

Each channel independently manages flow control. The sender must not transmit more data than the receiver's advertised window size allows, preventing any single channel from overwhelming the connection. The window is adjusted dynamically with SSH_MSG_CHANNEL_WINDOW_ADJUST messages.

Port Forwarding

SSH port forwarding (tunneling) is one of the protocol's most powerful features. It allows arbitrary TCP connections to be routed through the encrypted SSH tunnel, providing security for protocols that lack their own encryption and enabling access to services behind firewalls and NAT.

Local Port Forwarding (-L)

Local forwarding binds a port on the client machine and tunnels traffic to a destination accessible from the server. The syntax is:

ssh -L 5432:db.internal:5432 [email protected]

This makes localhost:5432 on your laptop connect to db.internal:5432 on the remote network — through the encrypted SSH tunnel. The database server never needs to be exposed to the internet. This is invaluable for accessing services behind NAT or firewalls.

Your Laptop localhost:5432 Encrypted SSH Tunnel Bastion SSH server db.internal port 5432 ssh -L 5432:db.internal:5432 user@bastion App connects to localhost:5432 → tunneled to db.internal:5432

Remote Port Forwarding (-R)

Remote forwarding is the inverse. The server binds a port and tunnels incoming connections back to a destination accessible from the client:

ssh -R 8080:localhost:3000 [email protected]

This makes public-server.com:8080 forward traffic back to your local development server on port 3000. This is how tools like ngrok work under the hood — exposing a local service to the internet through a reverse tunnel. It is also how machines behind NAT can make themselves reachable from outside: the machine behind NAT initiates the outbound SSH connection (which NAT permits), and the remote end listens for inbound connections to forward back through the tunnel.

Dynamic Port Forwarding (-D / SOCKS Proxy)

Dynamic forwarding creates a local SOCKS5 proxy that routes any TCP traffic through the SSH tunnel:

ssh -D 1080 [email protected]

Applications configured to use the SOCKS proxy at localhost:1080 will have their traffic tunneled through the SSH server. This is commonly used to securely browse the internet from an untrusted network (like public Wi-Fi) or to access region-restricted services. Browsers, curl (--proxy socks5h://localhost:1080), and many other tools support SOCKS5 natively.

SSH Agent and Agent Forwarding

The SSH agent (ssh-agent) is a daemon that holds decrypted private keys in memory. When an SSH client needs to authenticate, it asks the agent to perform the signing operation rather than reading the private key file directly. This means you only enter your passphrase once per login session, regardless of how many SSH connections you make.

# Start the agent and add a key
eval $(ssh-agent)
ssh-add ~/.ssh/id_ed25519

# On macOS, use the Keychain
ssh-add --apple-use-keychain ~/.ssh/id_ed25519

Agent forwarding (ssh -A or ForwardAgent yes in config) extends this across SSH hops. When you SSH into server A with agent forwarding enabled, and then SSH from server A to server B, the authentication request is forwarded back through the chain to your local agent. Your private key never exists on any intermediate server.

However, agent forwarding has a significant security risk: anyone with root access on the intermediate server can use your forwarded agent socket to authenticate as you to any server your key can access, for as long as the connection is alive. For this reason, ProxyJump is generally preferred over agent forwarding for multi-hop access.

ProxyJump and Bastion Hosts

A bastion host (or jump host) is a hardened server that serves as the sole entry point to a private network. Rather than exposing every internal server to the internet, administrators expose only the bastion and require all SSH connections to transit through it.

The modern way to traverse bastion hosts is ProxyJump (OpenSSH 7.3+):

# Command line
ssh -J bastion.example.com internal-server.example.com

# SSH config
Host internal-*
    ProxyJump bastion.example.com
    User admin

Host bastion.example.com
    User jumpuser
    IdentityFile ~/.ssh/bastion_key
Client ssh -J bastion Bastion TCP relay only Internal Server private network End-to-end encrypted tunnel (client to internal server) Bastion only sees encrypted bytes — no access to session content

ProxyJump is superior to agent forwarding because the bastion only relays encrypted TCP bytes between the client and the target. The SSH handshake happens end-to-end between the client and the internal server. The bastion never sees decrypted traffic, never holds your credentials, and cannot impersonate you to other servers. Chaining multiple jumps is also supported: ssh -J bastion1,bastion2 target.

SSH Certificates

As organizations grow, managing authorized_keys files on every server for every user becomes untenable. SSH certificates provide a scalable alternative. An organizational CA signs user and host keys, and servers/clients are configured to trust that CA rather than individual keys.

User Certificates

A user certificate is created by having the CA sign the user's public key:

# CA signs the user's public key
ssh-keygen -s /path/to/ca_key -I "[email protected]" \
  -n alice,root -V +52w ~/.ssh/id_ed25519.pub

The -I flag sets the certificate identity (for logging), -n restricts which usernames (principals) the certificate is valid for, and -V sets an expiration time. The server is configured to trust the CA:

# In /etc/ssh/sshd_config
TrustedUserCAKeys /etc/ssh/ca_user_key.pub

Now any user with a certificate signed by this CA can log in without their public key being in authorized_keys. Certificates can be short-lived (hours or days) and automatically issued by internal tooling, eliminating the need for long-lived keys.

Host Certificates

Host certificates solve the TOFU problem. Instead of blindly trusting a server's host key on first connection, the CA signs each server's host key. Clients are configured to trust the CA, so they can verify any server's identity on first contact:

# Sign the host key
ssh-keygen -s /path/to/ca_host_key -I "web1.example.com" \
  -h -n web1.example.com,10.0.1.5 /etc/ssh/ssh_host_ed25519_key.pub

# Client ~/.ssh/known_hosts
@cert-authority *.example.com ssh-ed25519 AAAA...CA_PUBLIC_KEY...

With host certificates, users never see the "Are you sure you want to continue connecting?" prompt for servers within the organization. This is a significant improvement for both security (no risk of accepting a rogue server on first connection) and usability (no manual fingerprint verification).

Certificates vs. Raw Keys

SSH certificates offer several advantages over distributing raw public keys:

Major organizations including Facebook, Netflix, and Uber use SSH certificate authorities internally. Open-source tools like step-ca (from Smallstep) and vault (from HashiCorp) automate certificate issuance.

SCP and SFTP

SSH provides two file transfer mechanisms, both running over the SSH connection layer.

SCP (Secure Copy Protocol) was the original file transfer tool. It uses a simple request-response protocol over a session channel. While functional, SCP has known parsing vulnerabilities and cannot resume interrupted transfers. OpenSSH 9.0 changed scp to use the SFTP protocol internally by default, and the legacy SCP protocol is considered deprecated.

SFTP (SSH File Transfer Protocol) is a full-featured file transfer subsystem. It supports directory listings, random-access reads and writes, file locking, symbolic links, permission changes, and resume of interrupted transfers. SFTP runs as a subsystem on a session channel, meaning it uses the same encrypted connection and authentication as your shell session. It is entirely separate from FTP despite the similar name — FTP uses its own protocol and typically runs unencrypted on ports 20/21.

# SFTP interactive session
sftp user@server

# Direct file operations
sftp user@server:~/file.txt ./local_copy.txt

# rsync over SSH (most common for sync operations)
rsync -avz -e ssh ./local/ user@server:/remote/

SSH over QUIC and Recent Developments

Traditional SSH runs over TCP, which means it suffers from TCP's limitations: head-of-line blocking (a lost packet stalls all channels), slow connection establishment (TCP handshake + SSH handshake), and poor behavior on lossy networks. Several proposals aim to address this.

SSH over QUIC has been explored in IETF drafts. QUIC provides multiplexed streams with independent loss recovery (no head-of-line blocking across channels), 0-RTT connection resumption, and built-in encryption via TLS 1.3. Mapping SSH channels to QUIC streams would let a file transfer and an interactive shell operate independently even during packet loss — a lost SFTP packet would not cause the shell to stutter.

However, as of 2026, SSH over QUIC remains experimental. Challenges include integrating QUIC's TLS 1.3 handshake with SSH's own key exchange and authentication model, handling UDP-based transport in environments where firewalls block non-TCP traffic, and the maturity of QUIC implementations. The Mosh (Mobile Shell) project has long solved the mobility problem with a UDP-based protocol that survives network changes and high latency, but it replaces the SSH session layer entirely rather than running SSH over QUIC.

Other recent OpenSSH developments include:

Hardening SSH

An SSH server exposed to the internet will be bombarded with brute-force login attempts within minutes. Hardening is not optional — it is a baseline requirement. Here is a practical checklist:

Disable Password Authentication

# /etc/ssh/sshd_config
PasswordAuthentication no
KbdInteractiveAuthentication no
UsePAM yes  # Keep PAM for session management, but auth is key-only

With password authentication disabled, brute-force attacks become meaningless. Attackers cannot guess a 256-bit Ed25519 key.

Restrict Key Algorithms

# Only allow modern algorithms
HostKeyAlgorithms ssh-ed25519,[email protected]
PubkeyAcceptedAlgorithms ssh-ed25519,[email protected],[email protected]
KexAlgorithms [email protected],curve25519-sha256

Disabling RSA and ECDSA eliminates attack surface from older algorithms. If legacy compatibility is required, allow rsa-sha2-512 but never the SHA-1 based ssh-rsa.

Rate Limiting and Fail2Ban

fail2ban monitors SSH auth logs and temporarily bans IP addresses after repeated failed login attempts. Even with password auth disabled, this reduces log noise and resource consumption from bot scanners:

# /etc/fail2ban/jail.d/sshd.conf
[sshd]
enabled = true
port = ssh
maxretry = 3
bantime = 3600
findtime = 600

OpenSSH's built-in MaxStartups directive also helps by throttling unauthenticated connections:

# Start dropping connections when 10 unauthenticated, rate 30%, max 60
MaxStartups 10:30:60

Additional Hardening Measures

Putting It All Together

SSH's layered architecture gives it remarkable versatility. A single protocol handles interactive terminals, file transfers, TCP tunneling, VPN-like functionality, and Git operations — all with strong encryption and authentication. The addition of certificates, FIDO2 hardware keys, and post-quantum key exchange keeps the protocol evolving while maintaining backward compatibility.

When you type ssh user@server, this is everything that happens in roughly one second:

  1. TCP three-way handshake to port 22.
  2. Version string exchange.
  3. Algorithm negotiation (KEXINIT).
  4. Curve25519 (or hybrid post-quantum) key exchange with forward secrecy.
  5. Host key verification against known_hosts (or certificate CA).
  6. Symmetric encryption activated (AES-256-GCM or ChaCha20-Poly1305).
  7. User authentication (Ed25519 public key signature, FIDO2 touch, or certificate).
  8. Session channel opened, pseudo-terminal allocated, shell started.

The entire protocol stack — from key exchange to channel multiplexing — is documented in four core RFCs: 4251 (architecture), 4252 (authentication), 4253 (transport), and 4254 (connection). Together, they define one of the most widely deployed and battle-tested security protocols on the internet.

You can see how SSH connections relate to internet routing by exploring the network paths between you and your servers. Try looking up your server's IP address in the BGP looking glass to see which autonomous systems carry your SSH traffic, or explore the routes of major hosting providers like AWS (AS16509), Google Cloud (AS15169), or Amazon (AS14618).

See BGP routing data in real time

Open Looking Glass
More Articles
How TLS/HTTPS Works: Securing the Internet's Traffic
Certificate Transparency: How CT Logs Secure the Web's PKI
How Firewalls Work: Packet Filtering, Stateful Inspection, and Beyond
What is Cross-Site Scripting (XSS)?
What is Cross-Site Request Forgery (CSRF)?
What is Server-Side Request Forgery (SSRF)?