2026-07-04
You're at an airport, a hotel, or a corporate office. Outbound 443 works; nothing else does. You want SSH home. You could stand up nginx stream with ssl_preread, or HAProxy in TCP mode, or Caddy layer4 — but that's a lot of framework for a demux. sslh is the ~2000-line C daemon designed for exactly this: listen on one port, peek at the first bytes of every incoming connection, and forward each to a different backend based on the protocol it detected.
Point sslh at 443, tuck sshd, nginx, and OpenVPN behind it on loopback, and everyone's happy:
# /etc/sslh/sslh.cfg
listen: ( { host: "0.0.0.0"; port: "443"; } );
protocols:
(
{ name: "ssh"; host: "127.0.0.1"; port: "22"; service: "ssh"; },
{ name: "openvpn"; host: "127.0.0.1"; port: "1194"; },
{ name: "tls"; host: "127.0.0.1"; port: "8443";
sni_hostnames: [ "www.example.com", "git.example.com" ]; },
{ name: "http"; host: "127.0.0.1"; port: "8080"; },
{ name: "anyprot"; host: "127.0.0.1"; port: "8080"; } # fallback
);
ssh -p 443 [email protected] works. https://example.com works. Both hit the same TCP port. sslh identifies protocols by fingerprinting the opening bytes:
SSH-2.0-…0x16 0x03 and carries an SNI extension it parsesanyprotThe killer feature: sslh never decrypts anything. It peeks the plaintext prelude, forwards, and gets out of the way. Your certificates stay on the backend, HTTP/2 and mTLS keep working end-to-end, and there's no TLS material on the demux box to steal.
Why not just nginx stream + ssl_preread? You can. But nginx forks workers, brings HTTP vocabulary you don't need, and eats real RAM at idle. sslh runs happily in ~500KB. On a Turris router, a $3/month VPS, or a hardened jump box, that's the difference between "runs" and "swaps".
Two binaries ship in the same package: sslh-fork (fork-per-connection, dead simple, great for a handful of long-lived sessions) and sslh-ev (libev event loop, scales to thousands of concurrent connections). Pick by load, not by fashion.
Three tricks worth knowing:
--transparent plus a TPROXY iptables rule preserves the real client IP on the backend. Without it, sshd's logs show every login from 127.0.0.1 and fail2ban becomes useless.--on-timeout ssh handles the awkward case where SSH is server-first: the client connects and waits silently for the banner, giving sslh nothing to sniff. After a brief timeout, sslh commits to SSH and the handshake proceeds.allow_from / deny_from — pin OpenVPN to your work CIDR while leaving HTTPS open to the world, all in one config.Install is apt install sslh or brew install sslh. Move whatever currently owns 443 to 127.0.0.1:8443, drop sslh in front, and you've quietly upgraded a single-port host into a full-service demux — without inviting a reverse proxy to the party.
