Skip to content
 
 

Repository files navigation

sshttp - hiding SSH servers behind HTTP

Unofficial fork / Stealth

This tree is an unofficial fork/continuation of the original sshttp. It is not the upstream project and should not be mistaken for an official release by Sebastian Krahmer.

Original source:

The original project was written by Sebastian Krahmer (<stealth@segfault.net>). We kept sshttpd close to the old algorithm on purpose: sniff the first bytes, keep the transparent socket trick, preserve the old flags, and let people who already scripted the fossil keep using it.

We always liked this code because it belongs to that older school where small programs did useful magic with kernel bits, conntrack and a little protocol knowledge. Today you can have a “SEC” talk refused because the proposal forgot a good AGENTS.md; this thing came from a time where the agent was you, awake at 03:00, trying not to firewall your own SSH session. Packets were still packets. They still are.

Stealth is remembered on THC's memorial page:

ircsnet 2004
12:11 -!- stealth [~stealth@segfault.net] has joined #!segfault
12:11 < stealth> we had joy we had fun we had a rootshell on a sun

0. Intro

In case your FW policy forbids SSH access to the DMZ or internal network from outside, but you still want to use ssh on machines which only have one open port, e.g. HTTP, you can use sshttpd.

sshttpd can multiplex protocol pairs where the first client bytes are useful before the backend has to speak:

  • SSH/HTTP
  • SSH/HTTPS
  • SSH/SMTP (without SMTP multiline banners)
  • HTTPS SNI multiplexing
  • SSH/HTTPS with SNI multiplexing
  • TLS ALPN detours (Go port)
  • opt-in PostgreSQL, SOCKS5, RDP, Redis, MQTT and custom prefix routes (Go port)

It does not magically identify server-first protocols, UDP traffic, encrypted application data after TLS handshakes, ECH-hidden hostnames, or random binary soup. SNI/ALPN/prefix routes are reachability selectors, not authentication. sshttpd does not terminate TLS and is not a WAF. Backends remain the door guards. Packets are small, but they are lawyers.

Want a manual? Read the fucking source. internal/sshttp/ is the mux brain; sshttpd run -h and examples/sshttpd.toml are the knobs. Comments in the code still speak plain — no PDF priesthood required.

Ops knobs worth knowing: alive_timeout (default 30s idle reap on spliced sessions, 0 disables) and max_conns (default 1024, 0 unlimited). Quiet interactive SSH longer than alive_timeout gets cut — raise it or set 0 if your humans nap mid-session.

The Go port also ships ProjectDiscovery-style commands: run, reload, status, stop, init-config, check-config, version, and help. TOML config is supported via sshttpd init-config -o sshttpd.toml; legacy flags still work because old scripts have feelings too.

0.5 Live lab notes (haltman.io)

Field test of the Go port on production-ish iron, not a slide deck. Same old trick, newer coat: one public TCP port, HTTP and SSH clients, backends that still do the real work.

Role Where
Multiplexer 137.74.118.86 (mail.thc.org), sshttpd 0.4.0-go on :10080
HTTP backend Python http.server on :8080
SSH backend sshd on :2222
Clients 154.47.16.42, 146.70.144.62

Lab flags on purpose: -f -X -R - -U - (foreground, plain connect, no chroot/drop). Fine for proving the mux. Suspicious for a fortress. Screenshots live under .github/images/; full text is below because not everyone renders pretty PNGs, and packets were never about wallpaper.

Mux side — sshttpd + HTTP backend

sshttpd mux routing SSH and HTTP on port 10080

root@vps-71e671ff-37e8c2:~/sshttp# ./bin/sshttpd run -f -X -R - -U - \
>   -l 0.0.0.0 -L 10080 -H 8080 -S 2222 \
>   -debug -color always


              _ _     _   _
 ___ ___ _ __| | |__ | |_| |_ _ __
/ __/ __| '__| | '_ \| __| __| '_ \
\__ \__ \ |  | | | | | |_| |_| |_) |
|___/___/_|  |_|_| |_|\__|\__| .__/
                              |_|


   hiding ssh behind http v0.4.0-go
   haltman.io · github.com/haltman-io/sshttp · first fossil: 2011-12-09


[INF] using config file: sshttpd.toml
[INF] Using HTTP_PORT=8080 SSH_PORT=2222 and local port=10080. Staying foreground. Plain localhost lab mode.
[WRN] plain localhost lab mode enabled; transparent source spoofing is disabled
[WRN] privilege drop/chroot disabled; fine for a lab, suspicious for production
[INF] sshttpd started, ready to rock
[DBG] route decision proto=ssh port=2222 reason=ssh-banner sni= alpn= peek=33
[ROUTE] 154.47.16.42:48930 -> 137.74.118.86:2222 [proto:ssh] [t:18.496s] [bytes:3137/3053]
[DBG] route decision proto=ssh port=2222 reason=ssh-banner sni= alpn= peek=33
[ROUTE] 146.70.144.62:45054 -> 137.74.118.86:2222 [proto:ssh] [t:36.458s] [bytes:3937/4861]
[DBG] route decision proto=http port=8080 reason=http-method sni= alpn= peek=82
[ROUTE] 146.70.144.62:35240 -> 137.74.118.86:8080 [proto:http] [t:59ms] [bytes:82/304]

SSH client login via mail.thc.org port 10080

root@vps-71e671ff-37e8c2:~/sshttp# python3 -m http.server 8080 --bind 0.0.0.0
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...
137.74.118.86 - - [06/Aug/2026 06:38:02] "GET / HTTP/1.1" 200 -

What that means in plain packet: SSH- first bytes went to :2222, GET went to :8080, same public :10080. No TLS terminate, no password check in the mux — just a Layer-5 fork in the road.

Client side

curl HTTP 200 via mail.thc.org port 10080

SSH on the HTTP-looking port

┌──(root💀8lgm-NastyMuffin)-[~]
└─# ssh extencil@mail.thc.org -p 10080
Warning: Permanently added '[mail.thc.org]:10080' (ED25519) to the list of known hosts.
(extencil@mail.thc.org) Password:
Linux vps-71e671ff 6.12.95+deb13-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.12.95-1 (2026-07-04) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: sorry fedz, i want just girls in my house :)

[haltman.io | extencil@vps-71e671ff] ~ [137.74.118.86]
--#  Connection to mail.thc.org closed by remote host.
Connection to mail.thc.org closed.

OpenSSH spoke first with an identification string. The mux believed it. sshd on :2222 did the actual auth. As it should be.

HTTP on the same port

┌──(root💀8lgm-NastyMuffin)-[~]
└─# curl -i -k http://mail.thc.org:10080/
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.13.5
Date: Thu, 06 Aug 2026 06:38:02 GMT
Content-type: text/html
Content-Length: 118
Last-Modified: Thu, 06 Aug 2026 06:26:28 GMT

greetings from mail.thc.org             SSHTTP
greetings from www.eurocompton.net      SSHTTP
greetings from reads.phrack.org         SSHTTP

Same socket number, different first bytes, different backend. The old party trick still works in 2026. Reproduce it yourself — copy the lab flags above, point -H/-S at real backends, hit the public port. Do not cargo-cult these production IPs into your firewall scripts without reading the nf comments first.

1. Build

The current tree builds the Go port by default and writes the daemon to bin/sshttpd:

$ export PATH=/usr/local/go/bin:$PATH
$ make test
$ make build
$ ./bin/sshttpd version

The original C++ tree is archived under @legacy/ (zip).

Sample config: examples/sshttpd.toml. Everything else is in the tree. make test && make build, then either read internal/ or stop pretending you wanted a manual.

proudly sponsored by:

2. Setup for single host

This paragraph describes the setup where all services run on the same host as sshttpd itself. The muxing happens to the same IP/IP6 address that the outside connects arrive to, so basically just the ports are changing per detected service.

sshttpd is an easy to use OSI-Layer5 switching daemon. It runs transparently on HTTP(S) port (-L switch, default 80) and decides on incoming connections whether this is SSH or HTTP(S) traffic. If its HTTP(S) traffic, it switches the traffic to the HTTP_PORT (-H, default 8080) and if its SSH traffic to SSH_PORT (-S, default 22) respectively.

After the build it is time to pick the right netfilter script for setting up the fw rules. In iptables directory you will find the old style iptables version and in nft the new version for nft (but currently only using the compat layer).

You need to edit nf-setup script (nf6-setupif using IPv6) to match your network device and $PORTS (22 and 8080 are just fine for the SSH/HTTP case) and run it to install the proxy rules. Your sshd has to run on $SSH_PORT and your webserver on $HTTP_PORT. Thats basically it. Go ahead and run sshttpd (as root) and it will layer5-switch your traffic destinated to TCP port 80:

Take care when running nf-setup on a remote host in an SSH session - it will firewall port 22

# ./nf-setup
Using network device eth0
Setting up port 22 ...
Setting up port 8080 ...
# ./sshttpd -S 22 -L 80 -H 8080 -U nobody -R /var/empty
sshttpd: Using HTTP_PORT=8080 SSH_PORT=22 and local port=80. Going background. Using caps/chroot.
#

If you want to mux SMTP with sshttpd, just give 25 as -L parameter, 2525 as -H parameter, and setup your smtp daemon to listen on 2525. Then edit the nf-setup script to match these ports. In the Makefile, change the SMTP_DOMAIN and SSH_BANNER to your needs (SSH_BANNER must match exactly yours of the running sshd). SMTP/SSH muxing was tested with OpenSSH client and Postfix client and server.

When muxing IPv6 connections, the setup is basically the same; just use the nf6-setup script and invoke sshttpd with -6.

3. Transparent proxy setup

You can run sshttpd also on your gateway machine and transparently proxy/mux all of your HTTP(S)/SSH traffic to your internal LAN. To do so, run sshttpd with -T and use nf-tproxy rather than nf-setup as a template for your FW setup. Carefully read nf-tproxy so you don't lock yourself out of the network and all the network devices and IP addresses match your setup.

4. SNI Mux

With sshttpd you can also mux based on the HTTPS SNI. Just set up your nf-setup to contain the SNI ports (there are already samples) and invoke sshttpd with -N name:port e.g. sshttpd -S 22 -H 4433 -L 443 -N drops.v2:7350 to hide a sshd on 22 and a drops setup on port 7350 behind port 443, and at the same time serving your webserver from port 4433 to be visible to outside on port 443. This works because drops sets the SNI of drops.v2 in outgoing connects. Multiple -N switches are allowed so you could mux a lot of services via SNI. The ports/services must run all on the same machine where the original request was destinated to. If you just want to mux based on SNI, you can set the SSH port to 0 via -S 0.

5. Misc

You don't need to patch any of your ssh/web/smtp client or server software. It works as is. sshttpd runs only on Linux and needs IP_TRANSPARENT support. It would work without, but by using IP_TRANSPARENT it is possible to even have unmodified syslogs, e.g. the original source IP/port of incoming connections is passed as-is to the SSH/HTTP/SMTP servers.

Make sure the nf_conntrack and nf_conntrack_ipv4 or nf_conntrack_ipv6 modules are loaded. sshttpd is also a tricky anti-SSH0day (if ever:) and anti SSH-scanning/bruteforcing measurement. sshttpd has small footprint and was optimized for speed so it also runs on heavily loaded web servers.

Since version 0.24, sshttpd also supports multiple CPU cores. Unless -n 1 is used as switch, sshttpd binds one thread per CPU core, to better exploit the hardware if running on heavily used web servers. It still runs this fixed number of threads no matter how many 1000s connection it handles at the same time. sshttpd runs as nobody user inside a chroot() (configurable via -U and -R switch) if compiled with USE_CAPS. It can also distinguish between SSH and SSL sessions, you just have to use an LOCAL_PORT (-L) of 443 or 4433 and change the HTTP_PORT in the nf-setup script to match your webservers HTTPS port. You cannot mix HTTP/SSH and HTTPS/SSH in one sshttpd instance but you can run two sshttpd's to reach that goal: one on LOCAL_PORT 80 and one on LOCAL_PORT 443.

6. Alternative docu

As per 2017 it seems you have to provide alternative facts for everything, so here are some good writeups from other people for better understanding or in case my description was too brief:

About

SSH/HTTP(S) multiplexer. Run a webserver and a sshd on the same port w/o changes.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages