Safeguard
Supply Chain Security•

The SSH Protocol Explained: Transport, Authentication and Channels

SSH stacks three protocols on TCP port 22: an encrypted transport, user authentication and multiplexed channels. How each layer works and how to harden it.

Safeguard Security Team
38 min read

SSH (Secure Shell) is a protocol for secure remote login and other services over an untrusted network. It stacks three protocols on one TCP connection, usually port 22: a transport layer that authenticates the server and encrypts traffic, a user authentication layer, and a connection layer that multiplexes shells, file transfer and forwarding into channels.

The core standards, RFC 4250 to RFC 4254, date from January 2006, but almost every algorithm inside them has since been replaced. This post walks through each layer on the wire, follows that history to OpenSSH 10.6 (6 October 2026), explains the weakness classes as concepts, and gives the commands a defender uses to check their own servers. Protocol facts come from the RFCs, the IANA registries and the OpenSSH project's own pages and manuals.

Key takeaways

  • SSH is defined by RFC 4250 to RFC 4254 (January 2006), updated by RFC 8308, RFC 8332, RFC 8709 and RFC 9142, among others.
  • The transport runs a negotiated key exchange signed by the server's host key and derives separate keys per direction. The first exchange hash becomes the session identifier, and user signatures are bound to it, so they cannot be replayed elsewhere.
  • SSH is only as strong as the client's host key check. RFC 4251 allows unverified first connections and says they are open to an active man in the middle. Pin keys with a managed known hosts file, certificates or DNSSEC-signed SSHFP records (RFC 4255).
  • Port and agent forwarding are channel types that need their own policy; RFC 9987 (May 2026) says agents should not be forwarded by default.
  • OpenSSH has offered a post-quantum hybrid key exchange by default since 9.0 (April 2022), switched the default to mlkem768x25519-sha256 in 10.0 (April 2025), and since 10.1 warns when a connection falls back to a non post-quantum exchange.
  • Check what you offer with ssh -Q, ssh -G and sshd -T, and keep the SSH libraries inside your software and images patched.

What SSH is for: the layer and the standard

SSH replaced cleartext remote login tools. RFC 4251 describes it as "a protocol for secure remote login and other secure network services over an insecure network" and contrasts it with telnet and rlogin, which sent passwords in the clear. Today the same protocol carries interactive shells, remote command execution for automation and CI, file transfer through the SFTP subsystem, Git over SSH, and tunnels for other TCP services.

SSH is an application layer protocol that runs over a reliable byte stream, which in practice means TCP. RFC 4253, section 4.1, says that when used over TCP/IP the server normally listens on port 22 and that the port is registered with IANA. The IANA service name registry lists ssh on port 22 for TCP, UDP and SCTP, referencing RFC 4251, but real deployments use TCP.

The five core RFCs

RFCTitleWhat it defines
RFC 4250SSH Protocol Assigned NumbersMessage numbers, disconnect codes and the IANA registries for algorithm and method names
RFC 4251SSH Protocol ArchitectureThe three layer design, data types, host key trust models, naming and the main Security Considerations
RFC 4253SSH Transport Layer ProtocolVersion exchange, binary packet format, algorithm negotiation, Diffie-Hellman key exchange, key derivation and rekeying
RFC 4252SSH Authentication ProtocolThe authentication framework and the publickey, password and hostbased methods
RFC 4254SSH Connection ProtocolChannels, flow control, sessions, X11 forwarding, environment passing and TCP/IP port forwarding

All five were published in January 2006 as Standards Track RFCs by the IETF secsh working group. They did not obsolete earlier RFCs, because the first version of the protocol, SSH-1, was never standardised. RFC 4253, section 5, notes that earlier versions "have not been formally documented" and that the only documentation of 1.x was the README files shipped with the original source code.

The RFCs that update them

RFCDateUpdatesWhat changed
RFC 4255January 2006(new record type)SSHFP DNS record for publishing host key fingerprints
RFC 5656December 2009(new algorithms)Elliptic curve key exchange and ECDSA host keys
RFC 8308March 2018RFC 4251, 4252, 4253, 4254Extension negotiation (SSH_MSG_EXT_INFO)
RFC 8332March 2018RFC 4252, RFC 4253RSA signatures with SHA-256 and SHA-512
RFC 8709February 2020RFC 4253Ed25519 and Ed448 public key algorithms
RFC 8731February 2020(new method)curve25519-sha256 and curve448-sha512 key exchange
RFC 9142January 2022RFC 4250, 4253, 4432, 4462Updated MUST, SHOULD and MUST NOT list for key exchange methods
RFC 9941April 2026(Informational)sntrup761x25519-sha512 hybrid key exchange
RFC 9987May 2026(Standards Track)The SSH agent protocol, including agent forwarding
RFC 10042August 2026(Informational)ML-KEM hybrid key exchange, including mlkem768x25519-sha256

The IANA "Secure Shell (SSH) Protocol Parameters" registry, last updated 8 September 2026 when fetched for this post, is the authoritative list of algorithm names and their implementation recommendations.

The three layers: transport, user authentication and connection

Almost every SSH security property follows from the order of the layers in RFC 4251.

  1. Transport layer (RFC 4253). Server authentication, confidentiality, integrity and optional compression, directly on TCP.
  2. User authentication layer (RFC 4252). Authenticates the user. It runs over the transport, so credentials are encrypted and the server is already authenticated when they are sent.
  3. Connection layer (RFC 4254). Multiplexes the tunnel into channels. Nothing in it is available until the user is authenticated.

After key exchange the client sends a service request for ssh-userauth, and each authentication request names ssh-connection as the service to start next. Message numbers are split by layer, which makes captures easy to read: 1 to 49 belong to the transport (KEXINIT is 20, NEWKEYS 21, EXT_INFO 7), 50 to 79 to user authentication, 80 to 127 to the connection protocol (CHANNEL_OPEN is 90), and 192 to 255 are for local extensions.

How SSH works on the wire

The full connection sequence

The diagram below follows a typical OpenSSH connection from TCP setup to an interactive shell. Message names are from RFC 4253, RFC 4252, RFC 4254 and RFC 8308.

Client                                              Server
  |------------------- TCP SYN to port 22 -------------->|
  |<------------------ TCP handshake completes --------->|
  |<----- "SSH-2.0-<software> <comments>" CR LF ---------|   identification strings
  |------ "SSH-2.0-<software>" CR LF ------------------->|   (sent in clear)
  |------ SSH_MSG_KEXINIT (cookie, algorithm lists) ---->|   algorithm negotiation
  |<----- SSH_MSG_KEXINIT (cookie, algorithm lists) -----|   (still in clear)
  |------ key exchange init (client ephemeral) -------->|   messages 30 to 49
  |<----- key exchange reply (host key, server        ---|
  |       ephemeral, signature over exchange hash H)    |
  |   client verifies host key against known_hosts,    |
  |   SSHFP or a CA, then checks the signature on H     |
  |------ SSH_MSG_NEWKEYS ----------------------------->|
  |<----- SSH_MSG_NEWKEYS ------------------------------|   encryption starts here
  |<----- SSH_MSG_EXT_INFO (server-sig-algs, ...) ------|   RFC 8308, optional
  |------ SERVICE_REQUEST "ssh-userauth" -------------->|
  |<----- SERVICE_ACCEPT -------------------------------|
  |------ USERAUTH_REQUEST user, "ssh-connection",  --->|
  |       "publickey", signature over session id       |
  |<----- USERAUTH_SUCCESS -----------------------------|
  |------ CHANNEL_OPEN "session", window, max packet -->|
  |<----- CHANNEL_OPEN_CONFIRMATION --------------------|
  |------ CHANNEL_REQUEST "pty-req" ------------------->|
  |------ CHANNEL_REQUEST "shell" --------------------->|
  |<====> CHANNEL_DATA / WINDOW_ADJUST (both ways) <===>|
  |------ CHANNEL_EOF, CHANNEL_CLOSE ------------------>|
  |<----- CHANNEL_CLOSE --------------------------------|
  |------ SSH_MSG_DISCONNECT -------------------------->|

Step 1: the version exchange

RFC 4253, section 4.2, requires both sides to send an identification string of the form SSH-protoversion-softwareversion SP comments CR LF, at most 255 characters. For this protocol the version must be 2.0. A server may send other lines first, which must not begin with SSH-; that allowance exists so TCP wrappers can show an error before disconnecting.

The identification strings are sent before any encryption, so anyone on the path can read the software name and version. They are also included in the key exchange hash, so a man in the middle who edits them breaks the exchange. A server that still announces the 1.99 compatibility version is running very old software.

Step 2: the binary packet protocol

Every message after the identification strings uses the binary packet format from RFC 4253, section 6:

FieldSizeMeaning
packet_lengthuint32Length of the packet in bytes, excluding the MAC and this field
padding_lengthbyteLength of the random padding
payloadpacket_length minus padding_length minus 1The message, compressed if compression was negotiated
random padding4 to 255 bytesRandom bytes so the total is a multiple of the cipher block size or 8; at least 4 bytes
macdepends on algorithmMessage authentication code, sent last and not encrypted

The MAC covers an implicit sequence number plus the unencrypted packet. The number starts at zero, rises with every packet and is never sent, so a deleted, replayed or reordered packet fails the check. RFC 4253 never resets it, even on rekeying, a detail that mattered in 2023 (see Terrapin below).

With encrypt-then-MAC modes (the -etm@openssh.com names) the MAC covers the ciphertext instead, and the OpenSSH sshd_config(5) manual says these "are considered safer and their use recommended".

Step 3: algorithm negotiation with KEXINIT

Each side sends SSH_MSG_KEXINIT. Its fields, from RFC 4253, section 7.1, are:

KEXINIT fieldPurpose
cookie (16 random bytes)Ensures neither side alone controls the keys and session identifier
kex_algorithmsKey exchange methods in preference order; also carries the ext-info and strict kex markers
server_host_key_algorithmsHost key signature algorithms the client accepts or the server holds keys for
encryption_algorithms, client to server and server to clientCiphers for each direction
mac_algorithms, both directionsIntegrity algorithms for each direction
compression_algorithms, both directionsnone or zlib variants
languages, both directionsNormally empty
first_kex_packet_followsWhether a guessed key exchange packet is sent immediately
reserved uint32Zero, for future extension

Choice is client driven: in each category the winner is the first algorithm in the client's list that the server also supports. If any category has no match, both sides disconnect. The server's list order does not matter, as the sshd_config(5) manual notes for KexAlgorithms.

Both KEXINIT messages are included in the exchange hash, so an attacker who strips a strong algorithm from one list changes the hash and the server's signature fails. What remains is honest negotiation of weak algorithms that both sides still list, so the fix is to stop offering them.

Step 4: key exchange, host key verification and the exchange hash

In the Diffie-Hellman exchange of RFC 4253, section 8, the client sends its public value e, and the server replies with its host public key K_S, its value f and a signature over the exchange hash H. Modern methods, including curve25519-sha256 and the post-quantum hybrids, keep that shape. H binds everything both sides said:

H = HASH( V_C || V_S || I_C || I_S || K_S || e || f || K )

V_C, V_S   client and server identification strings
I_C, I_S   client and server KEXINIT payloads
K_S        server public host key
e, f       client and server key exchange values
K          shared secret

The client makes two checks. The signature on H proves the server holds the private key for K_S. Whether K_S belongs to the intended host is a separate question the protocol cannot answer alone. RFC 4251 offers two trust models, a local name to key database (known_hosts) or a certification authority, and warns that skipping the check on first connection leaves the session open to an active man in the middle.

Step 5: key derivation, NEWKEYS and rekeying

The exchange produces the shared secret K and the hash H. The H from the first exchange becomes the session identifier and never changes for the life of the connection. Six values are derived with the negotiated hash, each with a different single letter label, so every direction gets its own key and IV:

Initial IV, client to server       HASH(K || H || "A" || session_id)
Initial IV, server to client       HASH(K || H || "B" || session_id)
Encryption key, client to server   HASH(K || H || "C" || session_id)
Encryption key, server to client   HASH(K || H || "D" || session_id)
Integrity key, client to server    HASH(K || H || "E" || session_id)
Integrity key, server to client    HASH(K || H || "F" || session_id)

Each side then sends SSH_MSG_NEWKEYS under the old keys, and every later message uses the new ones. Either side can start a new key exchange at any time by sending another KEXINIT. RFC 4253 recommends rekeying after each gigabyte of data or each hour of connection time, whichever comes first, and RFC 4251 adds that peers must rekey before the 32-bit sequence number wraps.

Step 6: extension negotiation (RFC 8308)

RFC 8308 lets each side announce capabilities after keys are in place. The client adds the marker ext-info-c and the server ext-info-s to their kex_algorithms lists; the markers never match, so they cannot be selected as a method. A peer that saw the marker may send SSH_MSG_EXT_INFO (message 7) after NEWKEYS. Its key extension, server-sig-algs, tells the client which signature algorithms the server accepts, so a client can use rsa-sha2-256 with an existing RSA key. OpenSSH supports ext-info-c since 7.2 and ext-info-s since 9.6.

The user authentication layer (RFC 4252)

Authentication is server driven. The client sends SSH_MSG_USERAUTH_REQUEST with a user name, the service to start (ssh-connection) and a method. The server answers USERAUTH_FAILURE, listing the methods that can continue and a partial success flag, or USERAUTH_SUCCESS once every required method is done.

The defined methods are:

MethodRFCStatus in RFC 4252How it proves identity
publickeyRFC 4252REQUIREDSignature with the user's private key over data that includes the session identifier
passwordRFC 4252OPTIONALCleartext password sent inside the encrypted transport
hostbasedRFC 4252OPTIONALThe client host signs on behalf of the user; trusts the client machine
keyboard-interactiveRFC 4256(separate RFC)Server driven prompts, often used for one time codes
GSS-API methodsRFC 4462(separate RFC)Kerberos through GSS-API; OpenSSH implements only the authentication part
noneRFC 4252NOT RECOMMENDEDUsed to query methods; accepted only if no authentication is required

The public key signature covers the session identifier, user name, service, algorithm and key, so RFC 4251 notes it "cannot be successfully replayed in other sessions". A password has no such binding: a compromised server, or an undetected man in the middle, learns a reusable credential.

RFC 4252 recommends a 10 minute authentication timeout and at most 20 failed attempts per connection, and lets a server answer for a non existent user with a plausible method list so it does not reveal which accounts exist. OpenSSH is stricter: LoginGraceTime defaults to 120 seconds and MaxAuthTries to 6.

The connection layer (RFC 4254): channels

Once authenticated, everything happens in channels. Either side opens one with SSH_MSG_CHANNEL_OPEN:

CHANNEL_OPEN fieldMeaning
channel typesession, x11, forwarded-tcpip, direct-tcpip, or an extension name
sender channelThe sender's local identifier for this channel
initial window sizeHow many bytes the peer may send before waiting for a window adjust
maximum packet sizeThe largest data packet the sender will accept
type specific dataFor example the host and port to connect to

Flow control is per channel: the receiver grants space with CHANNEL_WINDOW_ADJUST, so a bulk copy cannot starve a shell on the same connection. Data travels in CHANNEL_DATA, stderr in CHANNEL_EXTENDED_DATA, and a channel ends with CHANNEL_EOF and CHANNEL_CLOSE.

A session channel is shaped by channel requests: pty-req, env, x11-req, then one of shell, exec or subsystem (for example sftp). RFC 4254 flags env as a hazard in privileged processes and recommends an allow list, which OpenSSH implements as AcceptEnv.

How port forwarding works

Port forwarding is two channel types and one global request:

  • Local forwarding (ssh -L). The client listens locally and, per connection, opens a direct-tcpip channel naming a destination; the server connects to it from its own network position.
  • Remote forwarding (ssh -R). A tcpip-forward global request asks the server to listen; each arriving connection becomes a forwarded-tcpip channel back to the client, which must reject ports it never asked for.
  • Dynamic forwarding (ssh -D). The client runs a SOCKS proxy locally and opens a direct-tcpip channel per destination.

RFC 4254 says clients SHOULD reject direct-tcpip opens sent by a server. The bind address matters: localhost means loopback only, 0.0.0.0 or :: mean every interface, and OpenSSH's GatewayPorts default of no keeps remote forwardings on loopback.

How agent forwarding works

Agent forwarding, long an OpenSSH extension, was standardised as RFC 9987 in May 2026. The client sends an auth-agent-req@openssh.com (or advertised agent-req) request on a session; the server creates a user private socket and sets SSH_AUTH_SOCK; signature requests travel back over a channel to the client's agent.

The key cannot be copied this way, but it can be used. RFC 9987 says forwarding "inherently creates a transitive trust relationship" and should not be on by default. The ssh(1) manual warns that users who can bypass file permissions on the remote host can "perform operations on the keys", and suggests a jump host (-J) instead. Our post on whether SSH agent forwarding is safe covers the options in more depth.

Versions and history: from SSH-1 to post-quantum key exchange

SSH-1 and the move to SSH-2

SSH-1 was never written up as an RFC, and SSH-2 is a separate, incompatible protocol, which is why RFC 4253 defines the 1.99 compatibility version for the transition. OpenSSH's release notes record that 7.0 (11 August 2015) disabled SSH-1 at compile time, the 1024-bit diffie-hellman-group1-sha1 exchange and ssh-dss (DSA) keys by default; 7.6 (3 October 2017) deleted SSH-1 from the client; and 10.0 (9 April 2025) removed DSA entirely.

Algorithm updates that matter

RSA with SHA-2 (RFC 8332, March 2018). The original ssh-rsa signature uses SHA-1. RFC 8332 defines rsa-sha2-256 (RECOMMENDED) and rsa-sha2-512 (OPTIONAL) for the same keys, supported in OpenSSH since 7.2 (29 February 2016). OpenSSH 8.8 (26 September 2021) disabled SHA-1 RSA signatures by default because chosen-prefix collisions had become cheap. Existing RSA keys keep working with SHA-2: an "ssh-rsa" key is fine, an "ssh-rsa" signature is not.

Ed25519 (RFC 8709, February 2020). RFC 8709 formalises ssh-ed25519 and ssh-ed448. OpenSSH added Ed25519 in 6.5 and, per its specifications page, implements only the Ed25519 half. For key choice guidance see Ed25519 vs RSA SSH keys for developers.

Key exchange recommendations (RFC 9142, January 2022). RFC 9142 replaced the 2006 list: diffie-hellman-group1-sha1 drops from MUST to SHOULD NOT, diffie-hellman-group14-sha1 to MAY, diffie-hellman-group14-sha256 becomes the MUST, curve25519-sha256 and NIST curve ECDH are SHOULD, diffie-hellman-group-exchange-sha1 is SHOULD NOT and rsa1024-sha1 is MUST NOT.

Hybrid post-quantum key exchange. The OpenSSH post-quantum page warns that sessions recorded today could be decrypted later by a large enough quantum computer, which it calls "store now, decrypt later". The history:

VersionDateChange
OpenSSH 8.923 Feb 2022Adds sntrup761x25519-sha512@openssh.com (Streamlined NTRU Prime plus X25519) to the default list, after the ECDH methods
OpenSSH 9.08 Apr 2022Makes sntrup761x25519-sha512@openssh.com the default key exchange
OpenSSH 9.919 Sep 2024Adds mlkem768x25519-sha256 (FIPS 203 ML-KEM plus X25519), available by default; adds sntrup761x25519-sha512 without the vendor suffix
OpenSSH 10.09 Apr 2025Makes mlkem768x25519-sha256 the default; prefers AES-GCM over AES-CTR
OpenSSH 10.16 Oct 2025Client warns when a connection uses a non post-quantum key exchange; WarnWeakCrypto option
OpenSSH 10.46 Jul 2026Experimental ML-DSA 44 plus Ed25519 composite signature scheme, off by default
OpenSSH 10.66 Oct 2026Enables the ssh-mldsa44-ed25519 signature algorithm under its new name; adds WarnWeakCrypto to sshd

Both hybrids pair a post-quantum scheme with X25519, so the result is "no worse than the previous best classical algorithm", in the OpenSSH page's words. The specifications are now Informational RFCs: RFC 9941 (April 2026) and RFC 10042 (August 2026). IANA lists the ML-KEM hybrids and sntrup761x25519-sha512 as SHOULD implement.

Signatures carry no "store now, decrypt later" risk, the OpenSSH page notes; classical host and user keys just need retiring before a capable quantum computer exists, which the ML-DSA composite work in 10.4 and 10.6 starts.

Other changes in recent OpenSSH releases

OpenSSH 9.8 (1 July 2024) split out a per session sshd-session binary and turned on PerSourcePenalties, which temporarily refuses sources that cause failed logins, invalid user attempts or crashes. OpenSSH 10.0 moved authentication into a separate sshd-auth binary. OpenSSH 10.1 moved agent sockets from /tmp to ~/.ssh/agent and announced that a future release will ignore SHA-1 SSHFP records. OpenSSH 10.6 disabled the LZ77 dictionary coder in compression after researchers showed that a shared compression dictionary across channels can leak plaintext from one channel to an attacker who controls another; the release notes say this makes the Compression option less effective and recommend compressing at the application layer instead.

The 10.6 notes also promise more frequent releases, because many security reports now arrive with AI assistance.

Security properties and weakness classes

What SSH guarantees, and what it does not

PropertyProvided?Notes from the RFCs
ConfidentialityYes, after NEWKEYSVersion strings and algorithm lists are visible
IntegrityYes, unless the none MAC is usedSequence numbered MAC detects changes, deletion and reordering
Server authenticationOnly if the client verifies the host keyRFC 4251 calls unverified use "inherently insecure" against active attackers
Client authenticationYesPublic key signatures are bound to the session identifier
Replay protectionYesSequence numbers within, session identifier across sessions
Forward secrecyYes with Diffie-Hellman style exchangesPast sessions survive a later host key leak
AvailabilityNoKey exchange is costly and precedes authentication
Traffic analysis resistancePartialSizes and timing can reveal, for example, password length
Endpoint securityNoA compromised server exposes every session through it

Trust on first use and host key substitution

The largest practical weakness is not cryptographic. On a first connection the client has no stored key to compare. RFC 4251 describes the cases: an attacker on the path presents its own key and relays everything if the user accepts it; an attacker persuades users to trust a wrong key through a side channel; or an attacker tampers with an established session, which the MAC prevents unless the MAC algorithm is broken.

OpenSSH's StrictHostKeyChecking default, ask, prompts for new keys and refuses changed ones; accept-new adds new keys silently; no accepts changed keys too. The real fix is a trustworthy key before the first connection: a managed ssh_known_hosts file, host certificates or DNSSEC-signed SSHFP records. RFC 4254 adds that agent, X11 and TCP forwarding should be disabled if a host key changes without explanation.

Downgrade and prefix truncation

KEXINIT lists are signed, but weak algorithms both sides still list can be chosen honestly. A subtler gap surfaced in December 2023: researchers showed that an attacker on the path could insert messages before encryption started and delete the same number of messages just after, because the unencrypted handshake and the sequence numbers were not fully protected. CVE-2023-48795 describes it as letting "some packets" be omitted so that "some security features" are downgraded. OpenSSH's 9.6 release notes call the impact "very limited": the main identified effect was deleting the EXT_INFO message, which could disable some keystroke timing protections. The fix is the "strict KEX" extension, which ends the connection on any unexpected message during key exchange and resets the sequence numbers after each key exchange. It only applies when both sides support it, so old peers keep the gap. Our Terrapin explainer covers the mechanism and the affected cipher modes.

Agent forwarding abuse

A forwarded agent lets anyone with enough access on the remote host sign as you while you are connected. RFC 9987 defines key lifetime and confirmation constraints that narrow this, but the structural fix is ProxyJump instead of forwarding. Server side, AllowAgentForwarding defaults to yes, and the manual says disabling it "does not improve security unless users are also denied shell access".

Port forwarding as a policy bypass

RFC 4251 warns that forwarding can tunnel other protocols "undetectably" through a firewall, in either direction, so an SSH account is also network access from the server's position. PermitOpen, PermitListen and DisableForwarding narrow it.

Weak and legacy algorithms

Retire SHA-1 key exchange and signatures, 1024-bit Diffie-Hellman, DSA keys, CBC mode and 3DES ciphers, and MD5 MACs. RFC 4251 already flagged CBC mode's theoretical exposure to chosen ciphertext attacks in 2006. Every algorithm you offer is one negotiation might pick with an old peer.

Denial of service before authentication

RFC 4251 notes that key exchange is expensive and happens before authentication. OpenSSH's MaxStartups (default 10:30:100) drops unauthenticated connections as they pile up, and PerSourcePenalties refuses misbehaving sources, but limiting exposure is the real control.

Implementation bugs

Most SSH incidents are code bugs, not protocol flaws. The OpenSSH 9.8 release notes describe a critical sshd race condition in Portable OpenSSH 8.5p1 to 9.7p1 that could allow code execution as root. SSH libraries in applications and devices carry the same class of risk.

Secure configuration and how to check it

Only run the commands below against systems you own or are authorised to test. Every command and option comes from the OpenSSH manual pages linked in the sources.

Check what your client and server support

ssh -Q lists the algorithms a build supports. The ssh(1) manual lists the query terms, including cipher, cipher-auth, mac, kex, key, key-sig, sig and protocol-version:

ssh -Q kex
ssh -Q cipher
ssh -Q mac
ssh -Q key-sig
ssh -Q protocol-version

That is capability, not configuration. For the effective settings use ssh -G on the client and sshd -T on the server; sshd -T -C takes connection parameters (user, host, addr and others) to show what a Match block does:

ssh -G host.example.com | grep -Ei '^(kexalgorithms|ciphers|macs|hostkeyalgorithms|stricthostkeychecking|forwardagent)'

sudo sshd -T | grep -Ei '^(kexalgorithms|ciphers|macs|passwordauthentication|permitrootlogin|allowagentforwarding|allowtcpforwarding|gatewayports|x11forwarding|maxauthtries|logingracetime)'

sudo sshd -T -C user=deploy,host=ci.example.com,addr=192.0.2.10

Algorithm settings

The current sshd_config(5) manual gives this default for KexAlgorithms, which already puts the post-quantum hybrids first:

mlkem768x25519-sha256,
sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com,
curve25519-sha256,curve25519-sha256@libssh.org,
ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521

and this default for Ciphers:

chacha20-poly1305@openssh.com,
aes128-gcm@openssh.com,aes256-gcm@openssh.com,
aes128-ctr,aes192-ctr,aes256-ctr

On a current OpenSSH, leaving these defaults alone is usually safest. Trouble comes from old hand written lists that silently opt out of the hybrids. To restrict, remove items from the default with a leading - instead of replacing the list:

# /etc/ssh/sshd_config  (illustrative; check each name with ssh -Q first)
KexAlgorithms -diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha1
MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com

Then confirm with sshd -T. If an OpenSSH 10.1 or later client warns that the key exchange is not post-quantum, the server is old or has a restrictive KexAlgorithms list.

Authentication and access

Setting (sshd_config)Default per the manualHardened valueWhy
PasswordAuthenticationyesno, once keys or certificates workRemoves guessable, reusable credentials
PermitRootLoginprohibit-passwordnoForces named accounts and an audit trail
AuthenticationMethodsanypublickey, or publickey,keyboard-interactiveRequires specific methods
MaxAuthTries63 to 6Limits guesses per connection
LoginGraceTime120 seconds30 to 60 secondsFrees unauthenticated slots sooner
AllowUsers or AllowGroupsall usersnamed groupsNarrows who can log in at all

For hardening specific to internet exposed servers, see sshd settings for remote access from anywhere.

Forwarding

SettingDefaultSuggested
AllowAgentForwardingyesno where users only run commands; pair with no shell for real effect
AllowTcpForwardingyesno, or local, plus PermitOpen for specific targets
GatewayPortsnono
X11Forwardingnono
DisableForwarding(not set)yes for service accounts, with Match blocks for exceptions

On the client, ForwardAgent defaults to no. Keep it that way and use ProxyJump (ssh -J) for multi hop access, as shown in how to set up a bastion host for secure SSH access.

Pinning host keys

Get the real fingerprint on the server itself, as the ssh(1) manual shows:

ssh-keygen -l -f /etc/ssh/ssh_host_ed25519_key.pub

On a client, list accepted fingerprints or look up one host:

ssh-keygen -lv -f ~/.ssh/known_hosts
ssh-keygen -F host.example.com

Use StrictHostKeyChecking yes with a managed /etc/ssh/ssh_known_hosts for automation, and host certificates (RFC 4251's CA model) for larger fleets.

Publishing SSHFP records (RFC 4255)

The SSHFP record (DNS type 44) holds an algorithm number (IANA: 1 RSA, 2 DSA, 3 ECDSA, 4 Ed25519, 6 Ed448), a fingerprint type (1 SHA-1, 2 SHA-256) and the fingerprint. RFC 4255 says it MUST NOT be trusted unless authenticated with DNSSEC. The ssh(1) manual's workflow:

# on the server: print SSHFP records to add to the zone
ssh-keygen -r host.example.com.

# check the zone answers
dig -t SSHFP host.example.com

# connect, showing whether DNS matched
ssh -o "VerifyHostKeyDNS ask" host.example.com

VerifyHostKeyDNS defaults to no; yes trusts DNSSEC validated matches without prompting. Publish SHA-256 records only, since OpenSSH 10.1 announced that SHA-1 SSHFP records will be ignored in future. Build records from the server's key files rather than ssh-keyscan -D, whose manual warns that unverified network collection is open to a man in the middle.

Finally, keep sshd's LogLevel at its default INFO; the manual warns that DEBUG levels violate user privacy.

Which SSH implementations are inside your software

SSH code also lives where nobody edits sshd_config: base images that ship a Git client, CI runners, deployment tools, network appliances, and applications that open SSH or SFTP connections through libraries such as libssh, libssh2, Paramiko, Go's golang.org/x/crypto/ssh or Apache MINA SSHD. Each has its own release history and its own timing for fixes such as strict KEX, so a fully patched server can still run an application linked to an older SSH library.

That is where Safeguard fits. Its software composition analysis, generally available, inventories transitive dependencies in repositories and container images, so an SSH library three levels deep still shows up. SBOM generation and analysis in SPDX and CycloneDX, vulnerability management with EPSS and CISA KEV enrichment, reachability analysis and container scanning are also generally available and help you find and rank SSH library findings when an advisory lands. Safeguard does not inspect SSH traffic or replace host configuration review; the sshd -T checks above remain your job.

When to use SSH and when to use something else

NeedSSHRDPBastion (SSH jump host)Identity-aware access proxy
Typical useShell, commands, file transfer, Git, tunnelsGraphical Windows desktopOne controlled entry point to private SSH hostsPer request access based on user and device identity
AuthenticationKeys, certificates, passwords, GSS-APIWindows account credentialsSSH keys or certificates at bastion and targetsSingle sign on, device posture, short lived credentials
Main risksUnverified host keys, forwarding, password guessing on exposed port 22Exposed listeners, credential relay, drive and clipboard redirectionBastion is a high value target; agent forwarding through itProxy configuration; trust in the identity provider
Operational costLow: one daemon, one portMedium: Windows hosts plus a gatewayMedium: one more host to patch and logHigher: identity integration and policy design
Good default whenYou manage Linux, BSD, network devices or GitYou need a Windows desktopYou need SSH into private networks todayYou want to remove standing network access

They combine well: SSH to every server, only a bastion or proxy exposed, short lived SSH certificates instead of static keys, and no agent forwarding.

Common mistakes

  • Treating "ssh-rsa" as a key problem. OpenSSH 8.8 disabled SHA-1 RSA signatures, not RSA keys. Re-enabling ssh-rsa globally to fix one old device weakens every connection; scope it with a Host block if you must.
  • Hand written algorithm lists that never get updated. A KexAlgorithms line copied years ago can silently exclude the post-quantum defaults. Prefer the defaults or remove items with the - syntax.
  • Accepting host keys by habit. Typing "yes" to an unknown fingerprint is the exact moment RFC 4251 says the protocol cannot protect you. Distribute keys or use certificates.
  • Forwarding the agent to shared hosts. The key stays home but can be used by anyone with enough access on the remote host. Use ProxyJump.
  • Believing AllowTcpForwarding no isolates a shell user. The manual says users with shell access can install their own forwarders. Restrict the shell too.
  • Publishing SSHFP without DNSSEC. RFC 4255 says unsigned SSHFP records must not be trusted. Also move to SHA-256 records.
  • Turning on compression for mixed trust traffic. OpenSSH documentation already advised against it, and 10.6 reduced its effectiveness to close a cross channel leak. Compress in the application.
  • Patching sshd but not the libraries. Strict KEX has to exist on both ends, and applications and images often carry their own SSH code.

FAQ

What port does SSH use?

TCP port 22, registered with IANA for ssh and named in RFC 4253. Another port cuts scan noise but adds no cryptographic protection.

Is SSH encrypted?

Yes, from the first SSH_MSG_NEWKEYS onward, including user names and passwords. The version strings and algorithm lists sent before that are visible to anyone on the path.

What is the difference between SSH-1 and SSH-2?

SSH-2 is the layered protocol standardised in RFC 4250 to 4254, with negotiated algorithms and public key authentication bound to the session. SSH-1 was an earlier protocol that was never standardised, and the two are incompatible. OpenSSH disabled SSH-1 by default in 7.0 (2015) and removed it from the client in 7.6 (2017).

Is SSH quantum safe?

Key agreement can be. OpenSSH has used a hybrid post-quantum key exchange by default since 9.0 and uses mlkem768x25519-sha256 by default since 10.0, which protects recorded sessions against future decryption. Host and user key signatures are still mostly classical; OpenSSH added an ML-DSA 44 plus Ed25519 composite scheme in 10.4 and enabled it in 10.6.

How do I check which algorithms my SSH server supports?

Run sudo sshd -T on the server for the effective kexalgorithms, ciphers and macs; ssh -Q kex and friends show what the build supports. Only check servers you own or are authorised to test.

What is an SSHFP record?

An SSHFP record (RFC 4255, DNS type 44) publishes a hash of a server's SSH host key in DNS. A client with VerifyHostKeyDNS enabled compares it with the key the server presents. It is only trustworthy when the zone is signed with DNSSEC and the client validates it.

Is SSH agent forwarding safe?

Only to hosts you fully trust. The key is never copied, but while you are connected anyone with enough access there can use it to sign as you. ProxyJump avoids the need.

What was the Terrapin attack?

Terrapin (CVE-2023-48795, published 18 December 2023) let an attacker on the path delete a few messages at the start of an encrypted SSH session without detection, mainly the extension information message. OpenSSH 9.6 added a "strict KEX" extension that blocks it when both client and server support it.

Fix it without waiting for upstream

SSH weaknesses that reach production usually live in an implementation, not in RFC 4253: an sshd in a base image, an SSH or SFTP library in an application, or a deployment tool that bundles its own client. Those are dependencies you can find, upgrade and gate like any other.

Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.

3. Gold Registry: start from components that are already fixed

registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingThe Gold scan GitHub Action in CI, plus Safeguard policies and gates
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch (early access)
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

See how Safeguard would help

These demos show dependency, image and SBOM workflows. They do not monitor SSH sessions or check sshd_config. You can download Safeguard and follow the CLI docs to try them on your own projects.

This demo sets up the Safeguard runner on your own machine, approves it with its device code and then scans from the terminal. Pointed at a container image for a CI runner or deployment tool, the same scan lists the SSH packages and libraries inside it, such as an OpenSSH client or libssh2, with their versions and any known vulnerabilities.

Click through it at your own pace, or open it full screen.

This demo is a real CLI session: install, open the interactive terminal, ask it to fix the lodash CVE in a repository, and watch it read the finding and advisory, ask before each command, move lodash to 4.17.21, run npm install and npm test with approval, and print a summary without pushing anything. The same approve each step flow applies when the package to bump is an SSH library such as Paramiko or the Go SSH module pinned in your manifest.

Click through it at your own pace, or open it full screen.

This demo connects Claude to the Safeguard MCP server with OAuth and lets you choose which tools Claude can use. Once connected, you can ask in plain language which of your projects include a particular SSH library version after an advisory, and get the answer from your own scan data rather than from memory.

Click through it at your own pace, or open it full screen.

Quick check

An attacker records your SSH session and later steals the server's private host key. Can they decrypt the recording?

No, if the session used a Diffie-Hellman style key exchange, which all modern SSH key exchanges are. RFC 4251 explains that the session keys come from ephemeral values that are thrown away, so a later leak of the long term host key does not reveal past sessions. The host key only proves the server's identity during the handshake. A future quantum computer is a different threat, which is why hybrid post-quantum key exchange is now the OpenSSH default.

You set AllowTcpForwarding no on a server where users still have a normal shell. Have you stopped them tunnelling?

Not really. The sshd_config(5) manual says disabling TCP forwarding does not improve security unless users are also denied shell access, because they can install their own forwarders. Pair the setting with restricted shells, ForceCommand or DisableForwarding for accounts that do not need interactive access.

Sources

Last reviewed: 23 October 2026. OpenSSH defaults change between releases; confirm against the manual pages for the version you run.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.