Tools: sslh: Run HTTPS and SSH on Port 443 at the Same Time

Tools: sslh: Run HTTPS and SSH on Port 443 at the Same Time

How It Works

Installation

Quick Start: Command Line

Config File

Client Connection

Docker Compose

Adding OpenVPN

sslh vs Other Approaches

Things to Know

Summary

References The corporate or university network only allows ports 80 and 443 out. SSH on 22 is blocked.

Getting back to your home server means VPN or a web terminal, both annoying.

sslh lets your server accept both HTTPS and SSH on 443. The firewall sees 443. SSH is hidden inside it. sslh sits in front of port 443. When a connection arrives, it reads the first packet, identifies the protocol, and forwards to the right backend. From the SSH client's perspective, it connected to 443 and got SSH. From nginx's perspective, it's still listening on its own port. sslh sits between them. Supported protocols: SSH, TLS/HTTPS, HTTP, OpenVPN, SOCKS5, tinc, XMPP, and custom regex patterns. Test it first before setting up a service: Before running this, move nginx off 443 to 8443 (sslh now owns 443): For production, use a config file at /etc/sslh.cfg: After that, ssh myserver works without -p 443. HTTPS needs no changes — browsers connect to https://yourserver.com as usual. If you run services in Docker, sslh fits naturally as a container in front of nginx: Neither nginx nor sshd need to expose ports externally. sslh multiplexes everything. If OpenVPN is also blocked, sslh handles that too: Set your OpenVPN client's remote to yourserver.com 443. sslh identifies the OpenVPN handshake and forwards to 1194. If the goal is "SSH through a firewall," sslh is the most direct solution. If the target machine has no public IP at all, }}">reverse_ssh is a different approach worth knowing. Backend loses real client IP: By default sslh NATs connections, so backend services see 127.0.0.1 instead of the real client IP. If you need real IPs for fail2ban or access logs, enable transparent proxy mode — it requires extra iptables rules. Move nginx off 443 first: sslh needs to own 443. Whatever was listening there before needs to move to another port. CVE history: sslh has had a few CVEs over the years. Keep it updated; not a reason to avoid it. Multiple services, one port. sslh routes by inspecting the first packet. The most common setup is SSH on 443, getting through firewalls that only allow HTTPS. Once configured, SSH clients just change port from 22 to 443 — everything else stays the same. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Code Block

Copy

Incoming connection → 443 ↓ sslh (inspects first packet) ├── SSH packet → local :22 └── TLS packet → local nginx:443 Incoming connection → 443 ↓ sslh (inspects first packet) ├── SSH packet → local :22 └── TLS packet → local nginx:443 Incoming connection → 443 ↓ sslh (inspects first packet) ├── SSH packet → local :22 └── TLS packet → local nginx:443 # Ubuntu / Debian sudo apt install sslh # Arch sudo pacman -S sslh # macOS brew install sslh # Docker docker pull ghcr.io/yrutschle/sslh:latest # Ubuntu / Debian sudo apt install sslh # Arch sudo pacman -S sslh # macOS brew install sslh # Docker docker pull ghcr.io/yrutschle/sslh:latest # Ubuntu / Debian sudo apt install sslh # Arch sudo pacman -S sslh # macOS brew install sslh # Docker docker pull ghcr.io/yrutschle/sslh:latest # Port 443 accepts SSH (→ local 22) and HTTPS (→ local nginx 8443) sudo sslh --listen=0.0.0.0:443 \ --ssh=127.0.0.1:22 \ --tls=127.0.0.1:8443 # Port 443 accepts SSH (→ local 22) and HTTPS (→ local nginx 8443) sudo sslh --listen=0.0.0.0:443 \ --ssh=127.0.0.1:22 \ --tls=127.0.0.1:8443 # Port 443 accepts SSH (→ local 22) and HTTPS (→ local nginx 8443) sudo sslh --listen=0.0.0.0:443 \ --ssh=127.0.0.1:22 \ --tls=127.0.0.1:8443 # /etc/nginx/sites-available/default server { listen 8443 ssl; # moved from 443 # everything else stays the same } # /etc/nginx/sites-available/default server { listen 8443 ssl; # moved from 443 # everything else stays the same } # /etc/nginx/sites-available/default server { listen 8443 ssl; # moved from 443 # everything else stays the same } # /etc/sslh.cfg verbose: 0; foreground: false; inetd: false; numeric: false; transparent: false; # sslh listens here listen: ( { host: "0.0.0.0"; port: "443"; } ); # Protocol routing protocols: ( { name: "ssh"; host: "127.0.0.1"; port: "22"; log_level: 0; }, { name: "tls"; host: "127.0.0.1"; port: "8443"; log_level: 0; }, # catch-all: anything unrecognized goes to nginx { name: "anyprot"; host: "127.0.0.1"; port: "8443"; log_level: 0; } ); # /etc/sslh.cfg verbose: 0; foreground: false; inetd: false; numeric: false; transparent: false; # sslh listens here listen: ( { host: "0.0.0.0"; port: "443"; } ); # Protocol routing protocols: ( { name: "ssh"; host: "127.0.0.1"; port: "22"; log_level: 0; }, { name: "tls"; host: "127.0.0.1"; port: "8443"; log_level: 0; }, # catch-all: anything unrecognized goes to nginx { name: "anyprot"; host: "127.0.0.1"; port: "8443"; log_level: 0; } ); # /etc/sslh.cfg verbose: 0; foreground: false; inetd: false; numeric: false; transparent: false; # sslh listens here listen: ( { host: "0.0.0.0"; port: "443"; } ); # Protocol routing protocols: ( { name: "ssh"; host: "127.0.0.1"; port: "22"; log_level: 0; }, { name: "tls"; host: "127.0.0.1"; port: "8443"; log_level: 0; }, # catch-all: anything unrecognized goes to nginx { name: "anyprot"; host: "127.0.0.1"; port: "8443"; log_level: 0; } ); sudo systemctl enable sslh sudo systemctl start sslh sudo systemctl status sslh sudo systemctl enable sslh sudo systemctl start sslh sudo systemctl status sslh sudo systemctl enable sslh sudo systemctl start sslh sudo systemctl status sslh # Explicit port ssh -p 443 [email protected] # Or set it permanently in ~/.ssh/config Host myserver HostName yourserver.com Port 443 User yourname # Explicit port ssh -p 443 [email protected] # Or set it permanently in ~/.ssh/config Host myserver HostName yourserver.com Port 443 User yourname # Explicit port ssh -p 443 [email protected] # Or set it permanently in ~/.ssh/config Host myserver HostName yourserver.com Port 443 User yourname # docker-compose.yml services: sslh: image: ghcr.io/yrutschle/sslh:latest ports: - "443:443" command: > --foreground --listen=0.0.0.0:443 --tls=nginx:443 --ssh=sshd:22 depends_on: - nginx - sshd cap_add: - NET_RAW - NET_BIND_SERVICE restart: unless-stopped nginx: image: nginx:alpine # nginx doesn't expose 443 externally — sslh handles that volumes: - ./nginx.conf:/etc/nginx/nginx.conf sshd: image: linuxserver/openssh-server environment: - PUBLIC_KEY_FILE=/keys/authorized_keys volumes: - ./keys:/keys # docker-compose.yml services: sslh: image: ghcr.io/yrutschle/sslh:latest ports: - "443:443" command: > --foreground --listen=0.0.0.0:443 --tls=nginx:443 --ssh=sshd:22 depends_on: - nginx - sshd cap_add: - NET_RAW - NET_BIND_SERVICE restart: unless-stopped nginx: image: nginx:alpine # nginx doesn't expose 443 externally — sslh handles that volumes: - ./nginx.conf:/etc/nginx/nginx.conf sshd: image: linuxserver/openssh-server environment: - PUBLIC_KEY_FILE=/keys/authorized_keys volumes: - ./keys:/keys # docker-compose.yml services: sslh: image: ghcr.io/yrutschle/sslh:latest ports: - "443:443" command: > --foreground --listen=0.0.0.0:443 --tls=nginx:443 --ssh=sshd:22 depends_on: - nginx - sshd cap_add: - NET_RAW - NET_BIND_SERVICE restart: unless-stopped nginx: image: nginx:alpine # nginx doesn't expose 443 externally — sslh handles that volumes: - ./nginx.conf:/etc/nginx/nginx.conf sshd: image: linuxserver/openssh-server environment: - PUBLIC_KEY_FILE=/keys/authorized_keys volumes: - ./keys:/keys protocols: ( { name: "ssh"; host: "127.0.0.1"; port: "22"; }, { name: "openvpn"; host: "127.0.0.1"; port: "1194"; }, { name: "tls"; host: "127.0.0.1"; port: "8443"; }, { name: "anyprot"; host: "127.0.0.1"; port: "8443"; } ); protocols: ( { name: "ssh"; host: "127.0.0.1"; port: "22"; }, { name: "openvpn"; host: "127.0.0.1"; port: "1194"; }, { name: "tls"; host: "127.0.0.1"; port: "8443"; }, { name: "anyprot"; host: "127.0.0.1"; port: "8443"; } ); protocols: ( { name: "ssh"; host: "127.0.0.1"; port: "22"; }, { name: "openvpn"; host: "127.0.0.1"; port: "1194"; }, { name: "tls"; host: "127.0.0.1"; port: "8443"; }, { name: "anyprot"; host: "127.0.0.1"; port: "8443"; } ); - sslh GitHub Repository - sslh Configuration Documentation - OpenSSH Client Configuration (ssh_config man page)