sslh
Overview
This guide covers the deployment and configuration of sslh on Ubuntu 24.04 on Azure using SecureAnyCloud Azure Marketplace images. sslh is an open source SSL/SSH protocol multiplexer. It listens on a single port, reads the first bytes each client sends, works out which protocol is being spoken, and hands the connection to the matching service. The practical result is that SSH and HTTPS share one port.
That matters whenever the network between you and your server only lets 443 out. Hotel, airport, café and captive portal networks, tightly controlled corporate proxies and restrictive egress firewalls routinely allow 443 and block 22. With sslh in front, ssh -p 443 reaches your shell over the one port those networks do allow, while an ordinary browser hitting the same host and the same port still gets your web service. It is equally useful when you simply want one exposed port instead of several on an internet facing host.
The image compiles the latest stable sslh release from the official upstream source tag and bakes it reproducibly (the exact version and its provenance are recorded in /opt/sslh/VERSION). sslh runs under systemd as sslh.service and drops from root to an unprivileged sslh account as soon as it has bound the port.
A working multiplexer the moment it boots. This is not an empty daemon waiting to be configured. The image ships the backends too, so the appliance demonstrates itself standalone with nothing else to install: sslh routes SSH to the VM's own OpenSSH server, HTTPS to a local nginx TLS listener serving a status page, and plain HTTP to a local nginx HTTP listener. You then repoint those routes at your own services.
A per instance TLS certificate, and no key in the image. The HTTPS backend needs a certificate. Rather than baking one in, which would put the same private key on every customer VM, the image ships no certificate and no key at all. Both are generated on your VM at first boot, with Subject Alternative Names covering the VM public IP, hostname and loopback. Both long running services are gated so neither can start before that bootstrap has completed.
No default login of any kind. sslh has no credential to leak: it is a stateless connection router with no account, no password and no token. The only key material the appliance ever holds is the TLS private key described above, and that never ships.
What is included:
The latest stable sslh release, compiled from the official upstream source tag and run under systemd as sslh.service
A working four way routing table out of the box: ssh to the local OpenSSH server, tls to the local HTTPS backend, http to the local HTTP backend, and anyprot as the catch all
nginx providing both loopback backends, plus an unauthenticated /healthz load balancer probe and a 301 redirect to HTTPS on port 80
A per instance self signed TLS certificate generated on first boot, with the shipped image containing no certificate and no private key
sslh-selftest, a shipped command that opens all three protocols against the multiplexed port and tells you in one line whether each one routed correctly
The sslh-select and sslh-ev event driven backends installed alongside the default, so you can switch without recompiling
The complete corresponding GPL-2.0 source for the shipped binaries, on the image at /opt/sslh/src/
Ubuntu 24.04 LTS base with latest security patches applied at build time
Azure Linux Agent for seamless cloud integration and SSH key injection
24/7 SecureAnyCloud support with guaranteed 24 hour response SLA
Prerequisites
- An active Azure subscription, an SSH public key, and a VNet with a subnet in the target region
- A subscription to the sslh listing on the Azure Marketplace
- The services you eventually want to multiplex, if you plan to replace the shipped backends
Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) is ample. sslh is a small C daemon that does very little work per connection, so connection rate limits it rather than CPU or memory. For very high connection rates, use Standard_D2s_v5 or larger and consider the sslh-ev backend.
Deploy from the Azure Portal
Search the Azure Marketplace for sslh, select the SecureAnyCloud publisher, and select Create.
Add these network security group (NSG) rules, each from the networks you connect from:
- TCP 443, the multiplexed port that carries SSH, HTTPS and HTTP
- TCP 22, direct admin SSH, which stays available independently of the multiplexer
- TCP 80, the HTTP-to-HTTPS redirect and the health probe
Deploy from the Azure CLI
RG="sslh-prod"; LOCATION="eastus"; VM_NAME="sslh-01" IMAGE_URN="sac1647283583153:sslh:default:latest" SSH_KEY="$(cat ~/.ssh/id_rsa.pub)" az group create --name "$RG" --location "$LOCATION" az network vnet create -g "$RG" --name sslh-vnet --address-prefix 10.100.0.0/16 --subnet-name sslh-subnet --subnet-prefix 10.100.1.0/24 az network nsg create -g "$RG" --name sslh-nsg az network nsg rule create -g "$RG" --nsg-name sslh-nsg --name allow-mux --priority 100 \
--destination-port-ranges 443 --access Allow --protocol Tcp az network nsg rule create -g "$RG" --nsg-name sslh-nsg --name allow-ssh --priority 110 \
--source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp az network nsg rule create -g "$RG" --nsg-name sslh-nsg --name allow-http --priority 120 \
--destination-port-ranges 80 --access Allow --protocol Tcp
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:sslh:default:latest
az vm create -g "$RG" --name "$VM_NAME" --image "$IMAGE_URN" \
--size Standard_B2s --storage-sku StandardSSD_LRS \
--admin-username azureuser --ssh-key-values "$SSH_KEY" \
--vnet-name sslh-vnet --subnet sslh-subnet --nsg sslh-nsg --public-ip-sku StandardVerify the services and see which process owns which port
Connect over SSH on port 22. Confirm that the three services are active, then read the listener table. The table shows how the appliance is built: sslh alone holds the public port 443, while the backends it routes to are bound to the loopback interface, where nothing outside the VM can reach them directly.
sudo systemctl is-active sslh nginx ssh
sudo ss -tlnp | grep -E ':443|:8443|:8080' cat /opt/sslh/VERSIONsystemctl is-active prints active three times. The listener table shows sslh on 0.0.0.0:443 and nginx on 127.0.0.1:8443 and 127.0.0.1:8080. /opt/sslh/VERSION records the exact upstream tag the binary was compiled from.
Prove that one port is carrying three protocols
Open three connections to the same port 443 and watch each one land on a different service. The TLS connection reaches the HTTPS backend, the SSH connection receives a real OpenSSH protocol banner, and the plain HTTP connection reaches the HTTP backend:
curl -sk -o /dev/null -w 'TLS on 443 -> HTTP %{http_code}\n' https://127.0.0.1:443/
curl -sk https://127.0.0.1:443/ | grep -q 'TLS backend' && echo "TLS on 443 -> routed to the HTTPS backend" timeout 10 bash -c 'exec 3<>/dev/tcp/127.0.0.1/443; printf "SSH-2.0-sac_demo\r\n" >&3; IFS= read -t 8 -r l <&3; printf "%s\n" "$l"' | tr -d '\r'
curl -s http://127.0.0.1:443/ | grep -q 'HTTP backend' && echo "HTTP on 443 -> routed to the HTTP backend"The third command needs a short explanation. sslh identifies SSH from the client's own identification string, so the probe writes SSH-2.0-... first and then reads. The reply is the banner of the VM's OpenSSH server, for example SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.18. That banner shows that port 443 handed the connection to sshd.
The image also ships a single command that runs all three checks and fails with a clear message if any route is wrong:
sudo /usr/local/sbin/sslh-selftestOn success it prints one sslh-selftest OK: line that names each backend it reached. If a route is broken, it exits non-zero with a FAIL reason. Run it after every configuration change.
Connect over SSH on port 443 from your own machine
The previous steps ran on the VM. The value of sslh is what it lets you do from outside, over a network that only permits port 443. From your workstation, run:
ssh -p 443 azureuser@<public-ip> curl -k https://<public-ip>/Both commands use port 443. To make ssh -p 443 the default for this host, add an entry to your ~/.ssh/config:
Host my-sslh-host HostName <public-ip> User azureuser Port 443Port 22 stays open, so you keep direct administrative access when you change the multiplexer configuration.
Understand the routing configuration
One file, /etc/sslh/sslh.cfg, defines all of this behaviour. Read it:
sudo cat /etc/sslh/sslh.cfgThe listen block is the single public port. The protocols block is the routing table, and order matters: sslh tries each probe in turn and uses the first that matches, so the anyprot catch-all must stay last.
on-timeout: "ssh" covers protocols where the server speaks first and the client sends nothing. user: "sslh" is the unprivileged account that sslh permanently drops to once it has bound port 443.
Point sslh at your own services
The shipped backends let the appliance work standalone. To multiplex your own services, change the host and port of the tls and http entries to where your services listen. Those services can be on the VM or on another host, anywhere sslh can reach. Find the lines to edit:
sudo grep -nE 'name: "(tls|http)"' /etc/sslh/sslh.cfgEdit those lines in /etc/sslh/sslh.cfg. For example, point tls at 127.0.0.1 port 8443 on your own web server, or at another host. Then restart sslh to apply the change:
sudo systemctl restart sslhYou can run sudo /usr/local/sbin/sslh-selftest again afterwards. Its TLS and HTTP checks look for the shipped demonstration pages, so once you repoint those routes at your own services, the self test reports a mismatch. At that point, verify with curl against your own service instead.
Adding more protocols. sslh can also probe OpenVPN, XMPP, TINC, SOCKS and others. It can match on TLS ALPN or SNI so that several TLS services share the port. Add further entries to the protocols block in the same shape, and keep anyprot last. Run man sslh on the VM for the full list of probes and options.
Use your own domain and a trusted certificate
The per-instance self-signed certificate gets you started, but browsers warn about it. Point a DNS name that you control at the VM public IP, then install a certificate for that name:
sudo cp /path/to/fullchain-<your-domain>.pem /etc/nginx/tls/cert.pem
sudo cp /path/to/privkey-<your-domain>.pem /etc/nginx/tls/key.pem
sudo systemctl reload nginxIf you repointed the tls route at your own service instead of the shipped nginx backend, install the certificate on that service. Either way, sslh is unaffected. It never terminates TLS: it recognises TLS and forwards the connection untouched, so only the service that terminates TLS sees your certificate and private key.
Server components
Component Version / Detail
Multiplexer sslh (latest stable upstream tag, compiled reproducibly, see /opt/sslh/VERSION)
Binaries /usr/local/sbin/sslh (sslh-fork), plus sslh-select and sslh-ev
Public port 0.0.0.0:443 held by sslh, routing ssh, tls, http, anyprot
SSH backend the VM's own OpenSSH server on 127.0.0.1:22 (also reachable directly on port 22)
TLS backend nginx on 127.0.0.1:8443, per instance certificate in /etc/nginx/tls/
HTTP backend nginx on 127.0.0.1:8080
Front helper nginx on :80 (unauthenticated /healthz, 301 to HTTPS)
Configuration /etc/sslh/sslh.cfg
Self test /usr/local/sbin/sslh-selftest
Privileges binds 443 as root, drops permanently to the sslh account
Operating system Ubuntu 24.04 LTS (patched at build)
License GPL-2.0 (sslh), source at /opt/sslh/src/
Managing the sslh service
sudo systemctl is-active sslh nginx
sudo journalctl -u sslh --no-pager | tail -5Apply a configuration change with sudo systemctl restart sslh. Follow the logs live with sudo journalctl -u sslh -f. On first boot, the image writes a summary of this VM's endpoints to /root/sslh-credentials.txt. Read it with sudo cat /root/sslh-credentials.txt.
To log the protocol decision for each connection, add verbose-connections: 1; to /etc/sslh/sslh.cfg and restart. Check this log first if a client is routed to the wrong backend.
Switching backend. sslh-fork is the default and forks a process per connection. For very high connection rates, point the ExecStart line in /etc/systemd/system/sslh.service at /usr/local/sbin/sslh-ev or /usr/local/sbin/sslh-select. Both handle all connections in a single process. Then run sudo systemctl daemon-reload and restart sslh.
Security recommendations
sslh does not add authentication and does not terminate TLS. It only routes connections. Every backend keeps its own security, so keep SSH on key-only authentication and keep your TLS service's certificate configuration correct.
Restrict the NSG. Allow port 443 from the networks your users connect from. Restrict port 22 to your management range, so that you always have an administrative route that does not depend on the multiplexer.
Keep backends on the loopback. The shipped backends are bound to 127.0.0.1, so they are reachable only through sslh. If you repoint a route at another host, restrict that host's firewall to the sslh VM.
Plan for client IP addresses. Backends see connections from the sslh VM, not from the original client, because transparent mode is not enabled by default (it needs additional routing rules). If a backend needs the true client address, enable sslh's transparent proxy mode as described in man sslh, or use the PROXY protocol where the backend supports it.
Use a trusted certificate and your own domain (Step 8), so that clients validate the service without a manual trust step.
Keep the OS patched. Unattended security upgrades stay enabled on the running VM.
Support and Licensing
sslh is distributed under the GNU General Public License version 2. Because the GPL is a copyleft licence, this image carries both of the things that licence requires: the verbatim licence text at /opt/sslh/LICENSE, and the complete corresponding source for the binaries it runs at /opt/sslh/src/, with rebuild instructions in /opt/sslh/README.sac. sslh is redistributed unmodified.
SecureAnyCloud provides the packaging, the systemd integration, the working demonstration backends, the per instance TLS bootstrap, the self test tooling, and 24/7 support with a guaranteed 24 hour response SLA. sslh is an independent open source project by Yves Rutschle and this image is not affiliated with or endorsed by the sslh project or its authors.
Deploy on Azure
Find sslh on Ubuntu 24.04 on the Azure Marketplace, published by SecureAnyCloud. Deploy from the Portal or the Azure CLI as shown above.
Need Help?
Email sales@secureanycloud.com for deployment help, configuration questions, or licensing enquiries.
Deploy on Azure
Launch sslh on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com