I ran into sslh for the first time on an engagement where a client had exactly one externally reachable port — 443 — but needed to expose both an HTTPS web app and an SSH bastion behind restrictive corporate firewall rules. sslh is a protocol multiplexer that sits in front of multiple backend services and inspects the very first bytes of a connection to figure out what protocol is being spoken, then forwards the connection to the right backend — all on one shared port.
This article covers what sslh does, how its detection logic works internally, installation, configuration, real command examples, and how I’ve used it both defensively (as a network administrator would) and offensively (recognizing and working around it as a pentester).
What SSLH Does
sslh listens on a single TCP port (commonly 443) and demultiplexes incoming connections to different backend services based on the protocol being used, without requiring the client to do anything special. It can distinguish between:
- TLS/SSL (HTTPS, and by extension anything wrapped in TLS)
- SSH
- OpenVPN
- Plain HTTP
- XMPP
- Any other protocol you define with a byte-signature probe
This lets an organization run SSH and HTTPS (and more) all reachable on port 443, which is invaluable in restrictive network environments (hotel Wi-Fi, corporate proxies) where only 443 is allowed outbound.
Architecture and Internal Working
sslh works at the TCP level before any protocol-specific handshake logic kicks in:
- A client connects to the single exposed port.
sslhaccepts the connection and reads the first few bytes without immediately responding.- It runs each configured probe function against those bytes. A probe is protocol-specific detection logic — for example, the TLS probe checks for a valid TLS ClientHello structure (record type 0x16, correct version bytes), while the SSH probe checks for the
SSH-2.0banner-style signature. - Once a probe matches,
sslhopens a new connection to the corresponding backend service and proxies all further traffic transparently between the client and that backend. - If no configured probe matches within a timeout, the connection is either dropped or sent to a configured default (anyprotocol) backend.
sslh ships in a few different implementations: the classic sslh-select (event loop based on select()), sslh-fork (one process per connection), and sslh-ev (built on libev for higher performance).
Installation
Debian/Ubuntu:
sudo apt update
sudo apt install sslh -y
During installation on Debian-based systems, you’ll be prompted whether to run sslh as an inetd service or standalone daemon — standalone is usually the right choice for a dedicated multiplexer host.
From source:
git clone https://github.com/yrutschle/sslh.git
cd sslh
make
sudo make install
Verify:
sslh --version
Basic Syntax
sslh [options] --listen=address:port --ssh=address:port --tls=address:port [...]
Configuration File
sslh‘s primary configuration typically lives at /etc/sslh.cfg or /etc/sslh/sslh.cfg:
verbose: true;
foreground: false;
inetd: false;
numeric: true;
transparent: false;
timeout: 5;
listen:
(
{ host: "0.0.0.0"; port: "443"; }
);
protocols:
(
{ name: "ssh"; host: "localhost"; port: "22"; },
{ name: "tls"; host: "localhost"; port: "8443"; },
{ name: "http"; host: "localhost"; port: "8080"; }
);
Start the daemon with a specific config:
sudo sslh -F /etc/sslh.cfg
Command Examples
1. Command-line-only setup (SSH + TLS on port 443)
sudo sslh --listen=0.0.0.0:443 --ssh=127.0.0.1:22 --tls=127.0.0.1:8443
2. Adding an HTTP backend as well
sudo sslh --listen=0.0.0.0:443 --ssh=127.0.0.1:22 --tls=127.0.0.1:8443 --http=127.0.0.1:8080
3. Running in foreground with verbose logging (for debugging)
sudo sslh -F /etc/sslh.cfg -f -v
4. Testing which protocol is being routed
From a client, testing the SSH path:
ssh -p 443 user@example.com
Testing the HTTPS path:
curl -v https://example.com:443
Both connect to the same port 443 — sslh transparently routes each to the correct backend based on the initial handshake bytes.
5. Enabling transparent proxying (preserving original client IP)
transparent: true;
This requires additional iptables rules on Linux so backend services see the real client IP rather than sslh‘s local address:
sudo iptables -t mangle -N SSLH
sudo iptables -t mangle -A SSLH -j MARK --set-mark 0x1
sudo iptables -t mangle -A OUTPUT -p tcp -m mark --mark 0x1 -j ACCEPT
sudo ip rule add fwmark 0x1 lookup 100
sudo ip route add local 0.0.0.0/0 dev lo table 100
Real-World Use Cases
Bypassing restrictive network egress rules (legitimate admin use): Running SSH over port 443 through sslh lets remote staff on networks that only allow HTTPS still reach an SSH-based management interface.
Reducing external attack surface: Instead of exposing multiple ports (22, 443, 1194 for OpenVPN), an organization can expose only 443 externally, reducing the number of open ports visible to an external port scan.
Penetration testing recognition: When I scan a target and see only port 443 open, but a service on that port behaves inconsistently (sometimes an HTTPS handshake, sometimes an SSH banner appears after a specific byte sequence), that’s often a strong hint sslh (or something similar) is multiplexing multiple services behind it. Recognizing this changes my enumeration approach — I’ll specifically try SSH and OpenVPN handshakes against port 443 rather than assuming it’s HTTPS-only.
Red team infrastructure: Some red teams deploy sslh on C2 redirectors so that a single externally-facing port serves both a decoy HTTPS website and the actual C2 channel, making the infrastructure blend in better with normal web traffic during network monitoring.
Workflow and Tool Integration
# From the pentester side: confirm what's really behind port 443
nmap -sV -p 443 example.com
# Try an SSH handshake directly against 443
ssh -p 443 -v user@example.com
# Try a raw TLS handshake to compare behavior
openssl s_client -connect example.com:443
Comparing the behavior of these three commands against the same port is often how I first suspect sslh (or a similar multiplexer) is in play, rather than a single dedicated HTTPS server.
Performance Optimization
- Use
sslh-ev(the libev-based build) for high-connection-volume environments; it scales far better than theselect()-based build under heavy concurrent load. - Keep the
timeoutvalue tuned low enough to avoid resource exhaustion from slow-loris-style connections that never send enough bytes for probe detection. - Place
sslhon a dedicated lightweight host or container since it’s purely a proxy layer and shouldn’t compete for resources with the backend services it fronts.
Troubleshooting
- Connections hang before routing — check the
timeoutsetting; a probe that never matches will hold the connection open until timeout. - Backend sees sslh’s IP instead of the real client IP — enable
transparent: trueand configure the corresponding iptables/routing rules. - SSH clients failing to connect through sslh — confirm the SSH probe is enabled and that your SSH client isn’t sending anything non-standard before the banner that could confuse detection.
Best Practices and Common Mistakes
- Don’t forget transparent proxying configuration if backend services rely on real client IPs for logging or access control — without it, every connection will appear to originate from localhost.
- Test each protocol path individually after any configuration change; a syntax error in one protocol block can silently break routing for that specific service while others keep working.
- Keep
sslhitself patched — as a component sitting in front of your actual authentication and TLS termination, a vulnerability in the multiplexer itself would be a serious single point of failure.
FAQ
Does sslh terminate TLS itself? No — it only inspects the initial bytes to detect the protocol; the actual TLS handshake still terminates at the backend TLS service it forwards to.
Can sslh multiplex more than two protocols? Yes, it supports many named protocols (ssh, tls, openvpn, http, xmpp, and more) simultaneously on a single listening port.
Is running SSH over port 443 via sslh a security risk? Not inherently — it’s a legitimate technique for working around restrictive egress filtering, though it does mean the exposed port carries more “surface” than a single-purpose port would, so backend service hardening still matters.
Lab Example
In a lab VM, install sslh, an SSH server, and a simple Nginx HTTPS server, then configure:
listen: ( { host: "0.0.0.0"; port: "443"; } );
protocols:
(
{ name: "ssh"; host: "localhost"; port: "22"; },
{ name: "tls"; host: "localhost"; port: "8443"; }
);
Test both ssh -p 443 user@192.168.56.40 and curl -k https://192.168.56.40:443 and confirm each reaches the correct backend without any client-side special configuration.
Summary
sslh solves a real, common networking problem — needing to run multiple services behind a single restrictive port — through clean, protocol-aware demultiplexing at the TCP level. Understanding how its probes work is useful both as a network administrator deploying it and as a pentester learning to recognize when it’s quietly sitting in front of a target’s infrastructure.
References
- Official GitHub repository: https://github.com/yrutschle/sslh
- Man page:
man sslh