SSLH: Sharing a Single Port Between SSL and Non-SSL Services

sslh: Multiplexer for SSL and non-SSL connections

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:

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:

  1. A client connects to the single exposed port.
  2. sslh accepts the connection and reads the first few bytes without immediately responding.
  3. 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.0 banner-style signature.
  4. Once a probe matches, sslh opens a new connection to the corresponding backend service and proxies all further traffic transparently between the client and that backend.
  5. 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

Troubleshooting

Best Practices and Common Mistakes

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

Exit mobile version