BytePane

What Is SSH? Secure Shell Protocol Explained for Developers

Security18 min read

The Protocol That Replaced Plaintext Networking

In February 1995, a Finnish computer scientist named Tatu Ylönen discovered that a password sniffer had been installed on his university's network. The attacker had captured thousands of usernames and passwords — all transmitted in plaintext by Telnet, rsh, and rlogin, the standard remote access tools of the era. Within three months, Ylönen had written SSH (Secure Shell) to replace them. He released it publicly in July 1995, and within four months it had spread to over 20,000 users in 50+ countries.

Today, SSH is the backbone of server administration, DevOps, and cloud infrastructure. According to Landbase's 2026 technology dataset, 1,204 verified companies list SSH as a core infrastructure component — and that number reflects only companies that publicly declare it. In practice, virtually every Linux server on the internet runs OpenSSH. The SSH protocol suite is standardized in four IETF RFCs: 4251 (architecture), 4252 (authentication), 4253 (transport layer), and 4254 (connection protocol), all published in January 2006.

This guide covers how SSH actually works at the protocol level — not just "run ssh user@host." Understanding the internals makes you a better sysadmin, a better security reviewer, and better at diagnosing the failures that happen when SSH does not behave as expected.

Key Takeaways

  • SSH is a three-layer protocol: the transport layer handles encryption and host authentication; the authentication layer proves client identity; the connection layer multiplexes channels (shell, port forward, file transfer) over the single encrypted tunnel.
  • Key exchange uses Diffie-Hellman or Curve25519-Diffie-Hellman (X25519) to derive shared secret material without ever transmitting the session key — making it impossible to decrypt recorded sessions retroactively (perfect forward secrecy).
  • Ed25519 is the correct key type in 2026. It is faster, more compact, and has fewer implementation pitfalls than RSA-2048 or ECDSA. Generate with ssh-keygen -t ed25519.
  • Disable password authentication entirely (PasswordAuthentication no). Automated bots scan port 22 continuously — password auth is always one credential leak away from compromise.
  • CVE-2025-32433 (Erlang/OTP SSH, CVSS 10.0) allowed unauthenticated remote code execution — a reminder that SSH implementations, not just configuration, must be kept updated.

SSH Architecture: Three Protocols in One

SSH is not a single monolithic protocol. RFC 4251 defines it as three layered protocols that stack on top of each other, each with a distinct responsibility:

LayerRFCWhat It DoesRuns On Top Of
Transport LayerRFC 4253Key exchange, host authentication, encryption, integrity, compressionTCP (port 22)
User Auth LayerRFC 4252Authenticates the client to the server (publickey, password, keyboard-interactive)SSH Transport Layer
Connection LayerRFC 4254Multiplexes logical channels (shell, exec, subsystem, port-forward) over the tunnelSSH User Auth Layer

This layered design is why SSH can do so much: the same encrypted tunnel carries an interactive shell, SFTP file transfers, Git pushes, and multiple port-forwarded connections simultaneously, all multiplexed as separate channels at the Connection Layer.

The Connection Sequence: Step by Step

When you run ssh [email protected], here is precisely what happens:

1
TCP connection established
Your SSH client connects to port 22 (or the configured port) over TCP. Both sides exchange version strings: "SSH-2.0-OpenSSH_9.7" — the server announces its protocol version and software.
2
Algorithm negotiation
Client and server exchange lists of supported algorithms for key exchange, host key type, symmetric encryption, message authentication (MAC), and compression. They negotiate the best mutually-supported option for each. OpenSSH 9+ defaults to curve25519-sha256 for key exchange and [email protected] for encryption.
3
Key exchange (Diffie-Hellman or X25519)
This is the mathematical core of SSH security. Both sides perform a Diffie-Hellman exchange over an elliptic curve, each contributing random private values. The math produces a shared secret K that both sides independently compute — without ever transmitting K over the network. An eavesdropper who recorded every packet cannot derive K without solving the discrete logarithm problem on Curve25519.
4
Session keys derived
From the shared secret K and the exchange hash H, six keys are derived: one for encryption in each direction, one for integrity (MAC) in each direction, and optionally one for compression in each direction. The session is now encrypted and integrity-protected.
5
Host authentication (server to client)
The server signs the exchange hash H with its host private key (ed25519, ecdsa, or rsa). Your client checks the server's public key against ~/.ssh/known_hosts. If the key has changed since you last connected, SSH warns you: "WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!" — this is the man-in-the-middle detection mechanism.
6
User authentication (client to server)
Now the server authenticates you. With publickey auth: the server sends a challenge; your client signs it with your private key; the server verifies the signature against your ~/.ssh/authorized_keys. With password auth: your password is sent encrypted over the already-secured tunnel.
7
Channel opened, shell started
The Connection Layer opens a "session" channel. If interactive, the server starts a pseudo-terminal (PTY) and your shell. All keystrokes flow through the encrypted channel.

Algorithm Negotiation: What OpenSSH Uses by Default

Modern OpenSSH (9.x) has strong defaults. But understanding which algorithms are negotiated — and which are disabled — is essential for compliance requirements and debugging failed connections to legacy systems:

PurposePreferred (2026)Also SupportedRemoved/Deprecated
Key Exchangecurve25519-sha256diffie-hellman-group16-sha512diffie-hellman-group1-sha1 (Logjam)
Host Keyssh-ed25519rsa-sha2-512, ecdsa-sha2-nistp256ssh-dss (DSA), ssh-rsa (SHA-1)
Symmetric Encryption[email protected][email protected], aes128-ctr3des-cbc, arcfour, blowfish-cbc
MACImplicit (AEAD ciphers)hmac-sha2-256-etm, hmac-sha2-512-etmhmac-md5, hmac-sha1
View negotiated algorithms for an active SSH session
# See what algorithms your client and server negotiated
ssh -v [email protected] 2>&1 | grep -E "kex|cipher|mac|compression"

# Example output:
# debug1: kex: algorithm: curve25519-sha256
# debug1: kex: host key algorithm: ssh-ed25519
# debug1: kex: server->client cipher: [email protected] MAC: <implicit>
# debug1: kex: client->server cipher: [email protected] MAC: <implicit>

# Check supported algorithms on the server
ssh -Q kex           # key exchange algorithms
ssh -Q cipher        # symmetric ciphers
ssh -Q mac           # MAC algorithms
ssh -Q key           # host/user key types

SSH Key Types: Ed25519, RSA, ECDSA — Which to Use

The cryptographic properties of your SSH key type determine both security strength and operational characteristics. Ed25519 has been the right answer for new keys since OpenSSH 6.5 (2014). Here is why, and when the other types still matter:

Ed25519 (Recommended)

Ed25519 is an EdDSA (Edwards-curve Digital Signature Algorithm) scheme operating on Curve25519. Its advantages over RSA and ECDSA are substantial:

  • Fixed key size: always 256 bits (32 bytes). An Ed25519 public key is 68 characters in OpenSSH format. An RSA-4096 public key is 726 characters.
  • Faster: signature generation and verification are faster than RSA-2048 and competitive with ECDSA, per OpenSSH benchmarks.
  • Deterministic: the same message + key always produces the same signature. ECDSA requires a random nonce for each signature — a poor random number generator leaks the private key (the Sony PlayStation 3 hack, the Android Bitcoin wallet vulnerability).
  • No known timing side-channels in constant-time implementations (libsodium, OpenSSL 1.1.1+).
Generating and deploying SSH keys — production workflow
# Generate an Ed25519 key (recommended)
ssh-keygen -t ed25519 -C "[email protected]" -f ~/.ssh/id_ed25519

# If connecting to legacy systems that don't support Ed25519 (rare but exists):
ssh-keygen -t rsa -b 4096 -C "[email protected]" -f ~/.ssh/id_rsa_legacy

# View the key fingerprint (SHA-256 hash of the public key)
# This is what appears in authorized_keys audit logs
ssh-keygen -l -f ~/.ssh/id_ed25519
# 256 SHA256:abc123... [email protected] (ED25519)

# Copy public key to server
ssh-copy-id -i ~/.ssh/id_ed25519.pub [email protected]
# This appends your public key to ~/.ssh/authorized_keys on the server

# Verify: try connecting (should not prompt for password)
ssh -i ~/.ssh/id_ed25519 [email protected]

# Manual deployment (if ssh-copy-id not available)
cat ~/.ssh/id_ed25519.pub | ssh user@server "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys"

RSA: Still Viable, Use 4096 Bits

RSA keys are supported everywhere and remain secure at 4096 bits. The weak point is implementation complexity — RSA requires careful padding (PKCS#1 v1.5 is vulnerable to Bleichenbacher attacks when misused; use RSA-OAEP for encryption or RSA-PSS for signatures). In SSH specifically, OpenSSH uses RSA only for signatures (not encryption), and has used rsa-sha2-256/512 since OpenSSH 7.2 — no longer the vulnerable SHA-1 variant. Use RSA-4096 if your organization has compliance requirements specifying RSA, or if connecting to hardware that does not support Ed25519.

ECDSA: Avoid for New Keys

ECDSA on NIST curves (P-256, P-384, P-521) is faster than RSA but shares the random-nonce-dependency risk mentioned above. The NIST curves are also under ongoing scrutiny regarding potential backdoors in their generation parameters. If you are choosing between ECDSA and Ed25519, always prefer Ed25519. ECDSA's only advantage over Ed25519 is compatibility with FIPS 140-2 validated modules, which are required in some U.S. government environments.

SSH Authentication Methods Compared

Methodsshd_config OptionSecurityRecommendation
Public KeyPubkeyAuthentication yesExcellentUse exclusively
PasswordPasswordAuthenticationWeak (brute-force)Disable: set to no
Certificate (CA-signed)TrustedUserCAKeysExcellentBest for large fleets (centralized expiry)
GSSAPI (Kerberos)GSSAPIAuthenticationGood (SSO)For enterprise Active Directory integration
Keyboard-InteractiveKbdInteractiveAuthenticationVariesUsed for MFA/TOTP integration

SSH Certificate Authentication (for Teams)

For organizations with more than a handful of servers, managing individual public keys in authorized_keys files is operationally painful. A developer leaves; you must manually remove their key from 200 servers. SSH certificates solve this: a central Certificate Authority (CA) signs user keys with an expiry time. Servers trust the CA — not individual keys. Revocation is trivial.

SSH CA setup — sign user key with expiry
# 1. Create a CA key (stored securely, never distributed)
ssh-keygen -t ed25519 -f /etc/ssh/ca_key -C "Corp SSH CA"

# 2. Configure sshd to trust certificates from this CA
# /etc/ssh/sshd_config
TrustedUserCAKeys /etc/ssh/ca_key.pub

# 3. Sign a user's public key (8-hour certificate valid for 'deploy' principal)
ssh-keygen -s /etc/ssh/ca_key   -I "alice@corp-2026-05-01"      # certificate identity (for audit logs)
  -n "alice,deploy"               # valid principals (usernames on target servers)
  -V +8h                          # valid for 8 hours from now
  alice_id_ed25519.pub

# This creates alice_id_ed25519-cert.pub
# Alice uses it automatically: ssh -i alice_id_ed25519 user@server

# 4. Inspect the certificate
ssh-keygen -L -f alice_id_ed25519-cert.pub
# Type: [email protected] user certificate
# Valid principals: alice, deploy
# Valid: from 2026-05-01T09:00:00 to 2026-05-01T17:00:00
# Signature key: ED25519 SHA256:...

SSH Beyond Remote Login: SFTP, Tunneling, and Git

SFTP and SCP File Transfer

SFTP (SSH File Transfer Protocol) runs as an SSH subsystem — it is not related to FTP despite the name. Since OpenSSH 9.0, the scp command uses the SFTP protocol internally rather than the legacy SCP/RCP protocol, making it safer and more correct. The command-line interface for scp remains unchanged.

# Copy file to server
scp local-file.txt user@server:/remote/path/

# Copy file from server
scp user@server:/remote/path/file.txt ./local-file.txt

# Copy directory recursively
scp -r ./local-dir/ user@server:/remote/path/

# SFTP interactive session
sftp user@server
# sftp> ls, put, get, mkdir, rm, chmod

# rsync over SSH (preferred for large/repeated transfers)
rsync -avz --progress ./local-dir/ user@server:/remote/path/
# Resumes partial transfers; only syncs changed files

SSH Tunneling and Port Forwarding

SSH's Connection Layer supports three types of port forwarding, all tunneled through the encrypted SSH connection:

# LOCAL forwarding: access remote service via local port
# Use case: access a database on server that's only listening on localhost
ssh -L 5432:localhost:5432 user@server
# Now psql -h localhost -p 5432 connects through the SSH tunnel

# REMOTE forwarding: expose local port on the server
# Use case: test a local dev server from a remote machine
ssh -R 8080:localhost:3000 user@server
# Now server's port 8080 forwards to your local port 3000

# DYNAMIC forwarding: SOCKS5 proxy via the SSH server
# Use case: route browser traffic through the server's network
ssh -D 1080 user@server
# Configure browser to use SOCKS5 proxy at localhost:1080

# Keep tunnels alive in background
ssh -fNL 5432:localhost:5432 user@server
# -f: fork to background, -N: don't execute remote command

SSH Agent and Key Management

# Start ssh-agent (usually handled by your OS/desktop)
eval "$(ssh-agent -s)"

# Add key to agent (prompts for passphrase once)
ssh-add ~/.ssh/id_ed25519

# Add key that auto-removes after 4 hours
ssh-add -t 14400 ~/.ssh/id_ed25519

# List loaded keys
ssh-add -l

# Agent forwarding: use your local keys on a remote server
# (for jumping through bastion hosts, accessing private repos)
ssh -A user@bastion
# Now from bastion: ssh internal-server
# Uses your local key via agent forwarding — private key never leaves your machine

# ~/.ssh/config — cleaner than per-command flags
Host prod-bastion
  HostName 203.0.113.5
  User deploy
  IdentityFile ~/.ssh/id_ed25519
  ForwardAgent yes
  ServerAliveInterval 60    # keepalive every 60 seconds
  ServerAliveCountMax 3     # disconnect after 3 missed keepalives

Host internal-*
  ProxyJump prod-bastion    # automatically tunnel through bastion
  User appuser
  IdentityFile ~/.ssh/id_ed25519

Hardening sshd_config: The Settings That Matter

The default OpenSSH configuration is reasonably secure but leaves room for improvement. Per Shodan.io 2025 data, there are 24 million+ internet-accessible SSH servers — all facing automated scanners 24/7. These settings meaningfully reduce your attack surface:

/etc/ssh/sshd_config — production hardening
# Disable password authentication — most important setting
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no

# Disable root login (use sudo on the server)
PermitRootLogin no

# Enable only public key auth
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys

# Whitelist specific users (add more with space separation)
AllowUsers deploy alice bob

# Restrict to specific groups instead of listing all users
# AllowGroups sshusers

# Require SSH2 only (SSH1 removed in OpenSSH 7.0, this is belt-and-suspenders)
# Protocol 2 is now the only option in modern OpenSSH

# Restrict key exchange to modern algorithms
KexAlgorithms curve25519-sha256,[email protected],diffie-hellman-group18-sha512

# Restrict host key types
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256

# Restrict ciphers
Ciphers [email protected],[email protected],[email protected]

# Restrict MACs (AEAD ciphers make explicit MACs redundant, but for non-AEAD)
MACs [email protected],[email protected]

# Disconnect idle sessions
ClientAliveInterval 300
ClientAliveCountMax 2

# Limit login attempts before disconnecting
MaxAuthTries 3

# Reduce login grace time
LoginGraceTime 30

# Disable unused features
AllowAgentForwarding no  # only if you don't need agent forwarding
AllowTcpForwarding no    # only if you don't need port forwarding
X11Forwarding no

# After editing: validate config before reloading
# (to avoid locking yourself out)
sshd -t && systemctl reload sshd

One often-overlooked detail: file permissions on ~/.ssh are load-bearing. OpenSSH refuses to use a private key or authorized_keys file if the permissions are too permissive. The required permissions are documented in the Linux file permissions guide, and the BytePane chmod calculator can help verify the octal values.

Required SSH file permissions
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/id_ed25519         # private key: owner read/write only
chmod 644 ~/.ssh/id_ed25519.pub     # public key: world-readable is fine
chmod 644 ~/.ssh/known_hosts
chmod 600 ~/.ssh/config             # client config file

SSH Security Incidents: What Goes Wrong

CVE-2025-32433: Erlang/OTP SSH Remote Code Execution

In May 2025, a critical CVSS 10.0 vulnerability was disclosed in the Erlang/OTP SSH implementation. The flaw allowed arbitrary remote code execution without authentication on any service using the Erlang SSH library. According to Black Hat MEA 2026 analysis, approximately 70% of detections came from firewalls protecting OT (operational technology) networks — industrial control systems running Erlang-based management interfaces exposed on SSH. Affected products included RabbitMQ, CouchDB, and Riak nodes with SSH management enabled.

The lesson: SSH security is not just about configuration. The SSH implementation must also be kept patched. This applies to every SSH library in your stack — OpenSSH, libssh, Erlang/OTP, Apache MINA SSHD, and paramiko (Python).

SSH in Lateral Movement Attacks

According to MITRE ATT&CK incident analysis for 2024, SSH/SMB/Windows admin shares accounted for 14% of lateral movement techniques in reported incidents. Attackers who compromise one server look for SSH private keys in home directories, CI/CD system configuration, and cloud instance metadata. A single exposed key without a passphrase, combined with agent forwarding or password-less sudo, can cascade through an entire infrastructure.

Essential SSH Commands Quick Reference

# ── CONNECTING ──────────────────────────────────────────────
ssh user@host                          # Basic connection
ssh -p 2222 user@host                  # Non-standard port
ssh -i ~/.ssh/id_ed25519 user@host     # Specify identity file
ssh -v user@host                       # Verbose (debug connection issues)
ssh -vvv user@host                     # Very verbose

# ── REMOTE COMMAND EXECUTION ─────────────────────────────────
ssh user@host "df -h"                  # Run single command
ssh user@host "sudo systemctl status nginx"
ssh user@host < local-script.sh        # Run local script remotely

# ── FILE TRANSFER ────────────────────────────────────────────
scp file.txt user@host:/path/          # Upload file
scp user@host:/path/file.txt .         # Download file
rsync -avz ./dir/ user@host:/path/     # Sync directory (preferred)

# ── KEY MANAGEMENT ───────────────────────────────────────────
ssh-keygen -t ed25519 -C "comment"     # Generate key
ssh-copy-id user@host                  # Deploy public key
ssh-keygen -l -f ~/.ssh/id_ed25519     # Show fingerprint
ssh-keygen -p -f ~/.ssh/id_ed25519     # Change passphrase

# ── KNOWN HOSTS ──────────────────────────────────────────────
ssh-keyscan host >> ~/.ssh/known_hosts # Add host key (scripted)
ssh-keygen -R host                     # Remove host from known_hosts

# ── TUNNELING ────────────────────────────────────────────────
ssh -L local_port:dest_host:dest_port user@jump   # Local forward
ssh -R remote_port:local_host:local_port user@server  # Remote forward
ssh -D 1080 user@host                  # SOCKS5 proxy

# ── MULTIPLEXING (reuse existing connection, faster) ─────────
# ~/.ssh/config
Host *
  ControlMaster auto
  ControlPath ~/.ssh/cm-%r@%h:%p
  ControlPersist 10m

Frequently Asked Questions

What is SSH used for?

SSH is primarily used for secure remote login to Linux/Unix servers and executing commands over an encrypted connection. It also handles secure file transfer (SFTP/SCP), port forwarding and tunneling, and Git operations (GitHub, GitLab). SSH replaced Telnet and rsh because those protocols transmitted credentials in plaintext — interceptable by any network observer.

What port does SSH use?

SSH defaults to TCP port 22, registered with IANA for this purpose since 1995. Some admins move SSH to a non-standard port (like 2222) to reduce automated scan noise, but this is obscurity, not security — port scanners find it quickly. Use fail2ban and firewall IP allowlisting for real protection.

What is the difference between SSH1 and SSH2?

SSH1 (1995) had a CRC-32-based integrity check (vulnerable to insertion attacks) and RSA-only key exchange with known weaknesses. SSH2 (RFC 4251-4254, 2006) replaced the entire protocol: Diffie-Hellman key exchange, HMAC-based integrity, multiple algorithm support. OpenSSH dropped SSH1 in version 7.0 (2015). SSH2 is the only version in use.

What is the difference between SSH and SSL/TLS?

Both provide encrypted communication, but they operate differently. TLS is a security layer applied to existing protocols (HTTP→HTTPS, SMTP→SMTPS) and uses a certificate authority hierarchy for authentication. SSH is itself an application-layer protocol that manages both transport security and its own authentication via public keys or passwords — without a CA hierarchy.

Is SSH password authentication safe?

Passwords are transmitted encrypted, so they cannot be intercepted in transit. However, password auth is vulnerable to brute-force attacks — automated bots scan port 22 continuously. The right answer: disable password auth entirely (PasswordAuthentication no in sshd_config) and use public key authentication, which is immune to brute-force by design.

What SSH key type should I use in 2026?

Ed25519 — always, for new keys. It uses Curve25519, produces 256-bit keys, is faster than RSA-4096, is deterministic (no nonce-randomness risk like ECDSA), and has no known timing side-channels. Generate with: ssh-keygen -t ed25519 -C "[email protected]". Only use RSA-4096 if connecting to legacy systems that do not support Ed25519.

How do I harden an SSH server?

Core settings: PasswordAuthentication no, PermitRootLogin no, AllowUsers (whitelist), ClientAliveInterval 300, MaxAuthTries 3. Restrict algorithms to modern ones (curve25519 for key exchange, chacha20-poly1305 for encryption). Install fail2ban to block brute-force IPs. Restrict SSH access at the firewall level to known IP ranges where operationally feasible.

How does SSH key authentication work?

You generate a key pair: private key (stays on your machine) and public key (added to server's ~/.ssh/authorized_keys). When you connect, the server challenges you with a random value encrypted by your public key. Only your private key can produce the correct signed response. No secret is transmitted — authentication is proved by signature, not by a shared password.

Developer Tools for Your Workflow

Verify file permissions, generate secure passwords for SSH passphrases, and encode keys — all free, browser-side tools.