If you have Compression yes in your ~/.ssh/config or type ssh -C out of habit, your transfers may have quietly grown. OpenSSH 10.6, released on October 6, 2026, disabled the LZ77 part of SSH compression to close a side channel. The Compression option still exists and still negotiates zlib@openssh.com, but it now does far less work.
We wanted to know how much less, so we built OpenSSH 10.5p1 and 10.6p1 from source and pushed the same files through both. This guide covers what changed in the code, the numbers we measured, why the change was made, and what to use for bulk transfers now.
Quick Summary: What You'll Learn
- What OpenSSH 10.6 changed in SSH compression, straight from the source code.
- Measured wire bytes for
ssh -Con text, JSON logs and random data, 10.5 vs 10.6. - Why an older version number doesn't mean you still have the old behavior (Ubuntu 24.04 already backported it).
- The side channel in plain terms, and when compression is still unsafe even after the fix.
- What to use instead:
rsync -z,zstdin a pipe, and how to check what a session negotiated.
Did OpenSSH 10.6 make SSH compression weaker?
Yes, on purpose. The 10.6 release notes list it under "Potentially-incompatible changes": "compression will be less effective as a result of the change noted below in the Security section." The security entry says ssh and sshd now "disable LZ77 dictionary coder to mitigate the side-channel leaks" described in a 2026 paper titled "Crossing the Streams: SSH Plaintext Recovery via a Common Compression Context in Multiplexed Channels."
In the code, the change is one call in packet.c. OpenSSH 10.5 started outgoing compression with zlib's deflateInit(stream, 6), which is regular gzip-style deflate at level 6. OpenSSH 10.6 calls:
deflateInit2(&ssh->state->compression_out_stream, Z_BEST_SPEED, Z_DEFLATED, 15, 8, Z_HUFFMAN_ONLY) Z_HUFFMAN_ONLY is a zlib strategy that skips string matching entirely. Deflate normally does two things: LZ77 finds repeated strings and replaces them with short back-references to earlier data, then Huffman coding gives frequent symbols shorter bit codes. With Huffman only, the second step remains and the first is gone. Repetition, which is where most of deflate's gains come from on logs, JSON and source code, no longer helps.
The wire format is unchanged. Both ends still negotiate zlib@openssh.com, and a Huffman-only stream is a valid deflate stream, so an old client can decompress what a new server sends and the other way around. Nothing breaks. It just gets bigger.
How much bigger does ssh -C traffic get?
We measured it. The setup: OpenSSH 10.5p1 and 10.6p1 built from the official portable tarballs against the same zlib and OpenSSL 3.0.13, each used as a client to upload a file to a local sshd with Compression yes. Each run piped a file into ssh ... 'cat > /dev/null' and read two lines from ssh -v at exit: Transferred: sent N and compress outgoing: raw data X, compressed Y, factor F. Four test files:
- text: 16.2 MB of roff man page source from
/usr/share/man/man1. - seq: 22.9 MB from
seq 1 3000000. - json: 7.1 MB of JSON log lines (60,000 lines, same keys, varying values).
- random: 20 MB from
/dev/urandom.
Bytes on the wire with ssh -C (the factor is compressed size divided by raw size):
- text: 10.5 sent 4,338,600 bytes (factor 0.27). 10.6 sent 10,191,048 bytes (factor 0.63). That's 2.3 times more traffic.
- seq: 10.5 sent 6,139,392 (0.27). 10.6 sent 9,257,960 (0.40).
- json: 10.5 sent 449,860 (0.06). 10.6 sent 4,218,588 (0.60). More than 9 times more traffic.
- random: both 1.00. Random data never compressed, and both runs sent a few kilobytes more than the uncompressed run.
The pattern matches the code change. Data with long repeated strings, like JSON logs where every line repeats the same keys, lost almost all of its benefit, going from 6% of the original size to 60%. Data with a skewed character distribution but less exact repetition, like digits and newlines, kept more. Already-compressed or encrypted data was never helped and still isn't.
Two caveats. These are single runs on one machine, so treat the byte counts as solid and anything about speed as anecdotal. And compression applies per direction: each side compresses what it sends, so a 10.6 client uploading to an older server is weaker on the upload and unchanged on the download, until the server side is patched too.
On loopback, where bandwidth is effectively free, ssh -C only costs CPU. In our runs the 10.6 upload of the text file took less time than the 10.5 one, because Huffman-only is cheaper to compute than full deflate. On a real network the tradeoff is the opposite: you spend less CPU and send more bytes.
Why doesn't the version number tell you which behavior you have?
Because distributions backport security fixes into older versions. Our test box runs Ubuntu 24.04, and its client still reports OpenSSH_9.6p1 Ubuntu-3ubuntu13.20. Uploading the same text file with that client gave a factor of 0.63, identical to 10.6. Downloading from the Ubuntu sshd gave 0.63 as well.
The package changelog explains it. The 1:9.6p1-3ubuntu13.20 security update, dated October 8, 2026, includes "SECURITY UPDATE: incorrect use of LZ77 dictionary coder" with the note "Disable LZ77 dictionary coder" in packet.c, tracked as CVE-2026-106582.
So ssh -V is not a reliable check here. Other distributions may ship the backport on a different schedule, or not at all for older releases. Measure instead, as shown in the next section, or read your package changelog:
zcat /usr/share/doc/openssh-client/changelog.Debian.gz | grep -n -i lz77 That path is Debian and Ubuntu specific. On RPM-based systems, look for a similar entry with rpm -q --changelog openssh-clients | grep -i lz77; we only verified the Ubuntu package.
How do you check what compression a session actually uses?
ssh -v prints the negotiated compression method for each direction during key exchange, and the achieved ratio when the connection closes:
ssh -v -C user@host exit 2>&1 | grep -E 'compression:|compress (outgoing|incoming)' Output from our lab, trimmed:
debug1: kex: server->client cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: zlib@openssh.com debug1: kex: client->server cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: zlib@openssh.com debug1: compress outgoing: raw data 123, compressed 140, factor 1.14 debug1: compress incoming: raw data 514, compressed 450, factor 0.88 compression: none means compression is off for that direction. zlib@openssh.com means compression starts after authentication, which is the only mode current OpenSSH servers allow.
The factor line is the useful one. For a quick test of your own path, push something repetitive through it and look at the outgoing factor:
seq 1 1000000 | ssh -v -C user@host 'cat > /dev/null' 2>&1 | grep 'compress outgoing' For seq output, a pre-change client landed near 0.27 in our runs and a patched one near 0.40. For a real comparison, use a sample of the data you actually move.
What was the side channel, in plain terms?
LZ77 saves space by spotting text it has already seen and writing "copy 20 bytes from 300 bytes back" instead of the text itself. In SSH, all channels of one connection share a single compression context. That includes your shell, an scp transfer, and any port forward running over the same connection, including a ControlMaster-multiplexed one.
Now suppose an attacker can push data into one of those channels, for example through a forwarded port that serves web traffic they can influence. They send a guess at a secret that is flowing through another channel, like a token in your shell session. If the guess matches what the compressor saw recently, it compresses into a short back-reference and the encrypted packet is a little smaller. If it doesn't, the packet is a little larger. Encryption hides the content, not the length. By watching packet sizes and refining guesses, the attacker can recover the secret piece by piece.
If that sounds familiar, it's the same family as the CRIME and BREACH attacks on TLS compression. The OpenSSH release notes describe it as a chosen-plaintext attack in which "attacker-controlled input can recognizably reflect into the total length of transmitted ciphertexts." Removing the dictionary coder removes the back-references the attack relies on.
The man pages already warned about this before 10.6, and they still do. From ssh_config(5): "If untrusted traffic (such as an open port-forward) is permitted over the connection alongside trusted traffic, then compression may leak information about session contents. For this reason, it is not recommended to enable compression for connections that share trusted and untrusted traffic." That advice stands. Huffman-only coding still varies output size with the data, just much less, so don't treat 10.6 as a reason to turn compression back on for mixed-trust connections.
What should you use instead of ssh -C?
The release notes give the direction: "Users are encouraged to use application-level compression over the SSH protocol where possible, as this will typically be more effective and will be completely immune to this type of attack." It's immune because the compressor only ever sees your one stream, never another channel's data. In our lab it was also much smaller than old ssh -C ever was.
All numbers below use the 10.6 client with Compression=no, so the only compression is the application's own.
rsync -z
rsync compresses file data itself when you pass -z, and since rsync 3.2 both sides negotiate the algorithm from a list. Our rsync 3.2.7 build listed zstd lz4 zlibx zlib none, and plain -z picked zstd. Results for the 16.2 MB text file:
- no compression: 16,156,275 bytes sent
-
-z(negotiated zstd): 2,522,169 bytes -
--zc=zlib: 4,310,739 bytes -
--zc=lz4: 7,990,462 bytes
rsync -az ./site/ user@host:/srv/site/ # force an algorithm (implies -z); both ends must support it rsync -a --zc=zstd ./site/ user@host:/srv/site/ --zc is short for --compress-choice, and per the rsync man page it implies --compress unless you pass none. If the server's rsync is older and can't do the requested algorithm, it says so instead of silently falling back. Our SFTP vs rsync guide covers the rest of the rsync side, including the --delete trap.
zstd or gzip in a pipe
For one-off copies of a directory, compress the tar stream yourself and decompress on the other side:
tar -cf - ./logs | zstd -T0 -3 -c | ssh user@host 'zstd -d -c | tar -xf - -C /srv/archive' -T0 uses all cores, -3 is zstd's default level, -c writes to stdout and -d decompresses. We ran this exact pipeline in the lab and compared the files with cmp: identical. Wire bytes for single files, piped with Compression=no:
- text,
gzip -6: 4,314,564 bytes.zstd -3: 2,788,524. - json,
gzip -6: 449,068 bytes.zstd -3: 122,812.
gzip -6 lands almost exactly where pre-10.6 ssh -C did, which makes sense: same deflate algorithm, same level. zstd at its default level beat both by a wide margin. zstd has to exist on both ends. If it doesn't on the server, gzip and gunzip are everywhere.
The same idea works for database dumps and logs: compress where the data is produced.
ssh user@host 'pg_dump mydb | zstd -c' > mydb.sql.zst ssh user@host 'journalctl -u nginx --since today | gzip -c' > nginx.log.gz Turn compression off where it does nothing
If you have Compression yes under Host *, it now buys much less than it used to on most connections, and on fast links it never helped. The default is no. A tidy config keeps compression off globally and enables it only where it still earns its place:
Host slow-site-gw HostName gw.slow-site.example Compression yes Host * Compression no ssh uses the first value it finds for each option, so the specific Host block has to come before Host *. Our ssh config organization guide shows a layout that keeps that ordering readable.
When is ssh -C still worth it?
On genuinely slow links, a factor of 0.6 on text is still a 40% cut. The ssh(1) man page has always framed it this way: compression "is desirable on modem lines and other slow connections, but will only slow down things on fast networks." A shell session over a tethered phone, a satellite link or a congested VPN can still feel better with it. For anything bulk, application-level compression wins on both size and security.
Avoid it on connections that carry untrusted traffic next to trusted traffic. The usual cases are a jump host connection with port forwards for other people's traffic, or a multiplexed ControlMaster connection that mixes your shell with a forward to a public-facing service. If you route through a bastion, our ProxyJump setup guide shows how to keep hops separate.
On the server, sshd_config takes Compression yes, delayed (a legacy synonym for yes) or no, with yes as the default. A server that sets no turns compression off for every client (sessions simply negotiate none), which is a reasonable choice for a bastion that carries forwards.
Where does termique fit?
termique is a GUI SSH manager with its own SSH stack, so Compression lines in your ~/.ssh/config don't apply to it, and its docs don't list an SSH compression setting. The advice in this guide still applies inside termique's terminal: pipe through zstd or use rsync -z from the shell, and the compression happens in your application stream, not in the SSH layer.
What termique adds is the part around the session. Port forwards (local, remote and SOCKS5) are configured per host rather than living in a shell history, which makes it easier to see which connections carry forwarded traffic. Your SSH passwords and private keys are end-to-end encrypted before they sync between devices, so the sync server only stores ciphertext. Hosts, groups and snippets sync as plaintext metadata. The details are on our security page, and the wider hardening checklist is in our SSH security best practices guide.
FAQ
Is ssh -C slower after OpenSSH 10.6?
Not on CPU: Huffman-only coding is cheaper than full deflate, and in our loopback test the 10.6 upload finished faster than 10.5. What changes is size. Repetitive data like JSON logs sent several times more bytes in our test, so on a slow link transfers that relied on ssh -C take longer.
Does Compression yes in ssh config still do anything?
Yes. It still negotiates zlib@openssh.com and applies Huffman coding, which reduced our text sample to 63% of its size and the seq output to 40%. It no longer removes repetition, which was where most of the gain came from.
Is rsync -z or ssh -C better?
rsync -z. In our test it sent 2.5 MB for a 16.2 MB text file with negotiated zstd, against 10.2 MB for ssh -C on 10.6. It also compresses only rsync's own data, so it can't leak across SSH channels. Don't use both at once; compressing twice only costs CPU.
Does SSH support zstd compression?
Not in the SSH protocol as OpenSSH implements it. The only methods are zlib@openssh.com and none. To get zstd over SSH, compress in the application: rsync -z (which negotiates zstd when both ends have it) or a zstd pipe.
Why is my ssh -C transfer bigger on Ubuntu 24.04 when ssh -V still says 9.6?
Ubuntu backported the LZ77 change into its 9.6p1 package in the October 8, 2026 security update (1:9.6p1-3ubuntu13.20, CVE-2026-106582). The version string stays 9.6 while the compression behavior matches 10.6.
If you move files between more servers than you want to track by hand, try termique free: hosts and port forwards in one place, and credentials encrypted before they sync.