Applicative Protocol Multiplexer (e.g. share SSH and HTTPS on the same port) https://www.rutschle.net/tech/sslh/README.html
Find a file
2026-03-15 22:12:00 +01:00
.github/workflows CI: Do not parallelized container builds 2023-06-05 22:26:30 +02:00
doc fix syntax error in documentation example 2026-02-28 21:42:01 +01:00
hashtest refactor: make hash dump usuable for debugging 2025-08-10 23:12:24 +02:00
scripts add CAP_NET_RAW in systemd configuration to support transparent proxying 2026-02-03 21:00:40 +01:00
t test combinations of ipv4 <-> ipv6 over proxyprotocol 2026-02-06 22:05:27 +01:00
testgap new gap feature: hardlimit, with test suite 2025-06-22 16:45:50 +02:00
.gitignore Revert "include version.h in repo" 2025-05-04 11:45:33 +02:00
argtable3.c spelling: tentative 2023-07-30 01:43:12 -04:00
argtable3.h spelling: initialised 2023-07-30 01:43:12 -04:00
basic.cfg Make basic.cfg more useful (fix #438) 2024-04-21 18:20:54 +02:00
ChangeLog verbose_packet now dumps UDP packets as well (fix #527) 2026-03-15 22:12:00 +01:00
collection.c close file descriptors upon forking a connection shoveler (fix #521) 2026-02-15 11:17:36 +01:00
collection.h close file descriptors upon forking a connection shoveler (fix #521) 2026-02-15 11:17:36 +01:00
common.c Change IPv4 to IPv6 addresses when using proxyprotocol and protocols are different (Fix #515) 2026-02-01 18:02:03 +01:00
common.h Fix sslh-fork segmentation fault (Fix #508) 2025-10-30 08:22:48 +01:00
config.h.in merged proxyprotocol linking 2025-02-21 21:03:59 +01:00
configure merged proxyprotocol linking 2025-02-21 21:03:59 +01:00
configure.ac merged proxyprotocol linking 2025-02-21 21:03:59 +01:00
container-entrypoint.sh sync and resolve merge conflict 2023-08-09 23:36:01 +08:00
COPYING added license file 2014-03-30 18:09:16 +02:00
Dockerfile build sslh with libproxyprotocol 2025-12-20 17:23:10 +01:00
echo_test.cfg make UDP hash size configurable 2022-04-10 09:03:53 +02:00
echosrv-conf.c Prepare for 2.3.0 2025-09-10 15:50:52 +02:00
echosrv-conf.h Prepare for 2.3.0 2025-09-10 15:50:52 +02:00
echosrv.c remove obsolete getopt definitions (fix #489) 2025-05-29 15:46:08 +02:00
echosrv.cfg made echosrv independant from common.o and with its own configuration 2021-04-24 10:31:41 +02:00
echoѕrv-conf.h make UDP hash size configurable 2022-04-10 09:03:53 +02:00
example.cfg Prepare for 2.3.0 2025-09-10 15:50:52 +02:00
gap.c new gap feature: hardlimit, with test suite 2025-06-22 16:45:50 +02:00
gap.h new gap feature: hardlimit, with test suite 2025-06-22 16:45:50 +02:00
genver.sh Merge pull request #386 from oliv3r/dev/fix_version_tag 2023-07-30 23:12:32 +02:00
hash.c verify there is space in pid followup table before forking (fix #502) 2025-08-27 22:57:04 +02:00
hash.h verify there is space in pid followup table before forking (fix #502) 2025-08-27 22:57:04 +02:00
landlock.c Libwrap requires access to some more files when using landlock (fix #450 again) 2025-11-14 14:53:56 +01:00
log.c fix cppcheck complains 2023-08-31 15:10:53 +02:00
log.h Added support for logging to a file. 2022-04-28 15:19:18 +02:00
Makefile.in -V now outputs compile-time configuration (fix #519) 2026-03-04 22:35:56 +01:00
probe.c verbose_packet now dumps UDP packets as well (fix #527) 2026-03-15 22:12:00 +01:00
probe.h import main branch for endpoint tracking 2025-08-29 20:44:15 +02:00
processes.c close file descriptors upon forking a connection shoveler (fix #521) 2026-02-15 11:17:36 +01:00
processes.h close file descriptors upon forking a connection shoveler (fix #521) 2026-02-15 11:17:36 +01:00
proxyprotocol.c revert some useless changes to really fix #515 2026-02-06 22:08:59 +01:00
proxyprotocol.h proxyprotocol on client-side can be removed on server side, and its info is then logged 2025-09-10 12:14:41 +02:00
README.md added proxyprotocol documentation 2025-09-10 15:29:04 +02:00
sslh-conf.c Prepare for 2.3.0 2025-09-10 15:50:52 +02:00
sslh-conf.h Prepare for 2.3.0 2025-09-10 15:50:52 +02:00
sslh-ev.c close file descriptors upon forking a connection shoveler (fix #521) 2026-02-15 11:17:36 +01:00
sslh-fork.c Fix sslh-fork segmentation fault (Fix #508) 2025-10-30 08:22:48 +01:00
sslh-main.c -V now outputs compile-time configuration (fix #519) 2026-03-04 22:35:56 +01:00
sslh-select.c close file descriptors upon forking a connection shoveler (fix #521) 2026-02-15 11:17:36 +01:00
sslh.pod changed SSL to TLS in sslh.pod initial description 2024-09-08 16:56:50 +02:00
sslhconf.cfg import main branch for endpoint tracking 2025-08-29 20:44:15 +02:00
systemd-sslh-generator.c avoid useless strcpy (fix #440) 2024-05-11 17:01:48 +02:00
t_load update to Conf::Libconfig 1.0.3 API 2023-09-12 21:35:10 +02:00
t_old new test framework, with test classes for robustness and probe tests. Not all tests are ported yet. 2025-06-15 16:02:02 +02:00
tcp-listener.c close file descriptors upon forking a connection shoveler (fix #521) 2026-02-15 11:17:36 +01:00
tcp-listener.h sslh-ev: keep track of forked processes 2025-08-12 23:05:24 +02:00
tcp-probe.c verbose_packet now dumps UDP packets as well (fix #527) 2026-03-15 22:12:00 +01:00
tcp-probe.h sort target protocols as TCP or UDP, so only appropriate probes are called by the listeners 2022-05-05 17:45:40 +02:00
test.cfg import main branch for endpoint tracking 2025-08-29 20:44:15 +02:00
tls.c downgrade SNI matching logs from error to info to reduce log verboseness (Fix #520) 2026-02-08 09:50:45 +01:00
tls.h config file now read to struct with c2s; command line no longer works 2018-11-29 11:56:33 +01:00
TODO initiated TODO list 2013-10-06 12:09:52 +02:00
udp-listener.c check udp timeouts on all new connections including TCP 2025-08-15 19:54:51 +02:00
udp-listener.h check udp timeouts on all new connections including TCP 2025-08-15 19:54:51 +02:00
udp.cfg begin of release not for UDP 2020-12-06 15:50:08 +01:00

sslh -- A ssl/ssh multiplexer

sslh accepts connections on specified ports, and forwards them further based on tests performed on the first data packet sent by the remote client.

Probes for HTTP, TLS/SSL (including SNI and ALPN), SSH, OpenVPN, tinc, XMPP, SOCKS5, are implemented, and any other protocol that can be tested using a regular expression, can be recognised. A typical use case is to allow serving several services on port 443 (e.g. to connect to SSH from inside a corporate firewall, which almost never block port 443) while still serving HTTPS on that port.

Hence sslh acts as a protocol demultiplexer, or a switchboard. With the SNI and ALPN probe, it makes a good front-end to a virtual host farm hosted behind a single IP address.

sslh has the bells and whistles expected from a mature daemon: privilege and capabilities dropping, inetd support, systemd support, transparent proxying, support for HAProxy's proxyprotocol, chroot, logging, IPv4 and IPv6, TCP and UDP, a fork-based, a select-based model, and yet another based on libev for larger installations.

Install

Please refer to the install guide.

Security

Matthias Gerstner from OpenSUSE has performed a code review of sslh from a security point of view, which revealed a number of problems, including two CVE. His findings have already been taken partly into account for the more critical ones. The full review is well worth reading if you are using sslh in production.

Part of the securing your installation involves configuring connection limits. This is described in this guide.

Configuration

Please refer to the configuration guide.

Transparent proxying

Transparent proxying allows the target server to see the original client IP address, i.e. sslh becomes invisible.

The same result can be achieved more easily by using proxyprotocol if the backend server supports it. This is a simple setting to add to the sslh protocol configuration, usually with an equivalently simple setting to add in the backend server configuration, so try that first. This is explained in a separate document.

This means services behind sslh (Apache, sshd and so on) will see the external IP and ports as if the external world connected directly to them. This simplifies IP-based access control (or makes it possible at all), and makes it possible to use IP-based banning tools such as fail2ban.

There are two methods. One uses additional virtual network interfaces. The principle and basic setup is described here, with further scenarios described there.

There is also a guide to use podman.

Another method uses iptable packet marking features, and is highly dependent on your network environment and infrastructure setup. There is no known generic approach, and if you do not find directions for your exact setup, you will probably need an extensive knowledge of network management and iptables setup".

It is described in its own document. In most cases, you will be better off following the first method.

Docker image

How to use


docker run \
  --cap-add CAP_NET_RAW \
  --cap-add CAP_NET_BIND_SERVICE \
  --rm \
  -it \
  ghcr.io/yrutschle/sslh:latest \
  --foreground \
  --listen=0.0.0.0:443 \
  --ssh=hostname:22 \
  --tls=hostname:443

docker-compose example

version: "3"

services:
  sslh:
    image: ghcr.io/yrutschle/sslh:latest
    hostname: sslh
    ports:
      - 443:443
    command: --foreground --listen=0.0.0.0:443 --tls=nginx:443 --openvpn=openvpn:1194
    depends_on:
      - nginx
      - openvpn

  nginx:
    image: nginx

  openvpn:
    image: openvpn

Transparent mode 1: using sslh container for networking

Note: For transparent mode to work, the sslh container must be able to reach your services via localhost

version: "3"

services:
  sslh:
    build: https://github.com/yrutschle/sslh.git
    container_name: sslh
    environment:
      - TZ=${TZ}
    cap_add:
      - NET_ADMIN
      - NET_RAW
      - NET_BIND_SERVICE
    sysctls:
      - net.ipv4.conf.default.route_localnet=1
      - net.ipv4.conf.all.route_localnet=1
    command: --transparent --foreground --listen=0.0.0.0:443 --tls=localhost:8443 --openvpn=localhost:1194
    ports:
      - 443:443 #sslh

      - 80:80 #nginx
      - 8443:8443 #nginx

      - 1194:1194 #openvpn
    extra_hosts:
      - localbox:host-gateway
    restart: unless-stopped

  nginx:
    image: nginx:latest
    .....
    network_mode: service:sslh #set nginx container to use sslh networking.
    # ^^^ This is required. This makes nginx reachable by sslh via localhost
  
  openvpn:
    image: openvpn:latest
    .....
    network_mode: service:sslh #set openvpn container to use sslh networking

Transparent mode 2: using host networking

version: "3"

services:
  sslh:
    build: https://github.com/yrutschle/sslh.git
    container_name: sslh
    environment:
      - TZ=${TZ}
    cap_add:
      - NET_ADMIN
      - NET_RAW
      - NET_BIND_SERVICE
    # must be set manually
    #sysctls:
    #  - net.ipv4.conf.default.route_localnet=1
    #  - net.ipv4.conf.all.route_localnet=1
    command: --transparent --foreground --listen=0.0.0.0:443 --tls=localhost:8443 --openvpn=localhost:1194
    network_mode: host
    restart: unless-stopped
  
  nginx:
    image: nginx:latest
    .....
    ports:
      - 8443:8443 # bind to docker host on port 8443

  openvpn:
    image: openvpn:latest
    .....
    ports:
      - 1194:1194 # bind to docker host on port 1194

Comments? Questions?

You can subscribe to the sslh mailing list here: https://lists.rutschle.net/mailman/listinfo/sslh

This mailing list should be used for discussion, feature requests, and will be the preferred channel for announcements.

Of course, check the FAQ first!