wstunnel
Tunnel anything through the one port firewalls leave open
scrollTunnel anything through the one port firewalls leave open.
wstunnel wraps TCP, UDP, SOCKS5 and more inside WebSocket, HTTP/2 or WebTransport, so your traffic walks through corporate proxies, captive networks and deep packet inspection looking like ordinary web traffic. One static Rust binary on each side, nothing else to install.
Up in two commands
One binary plays both roles. Start a server where you have unrestricted internet, then point a client at it from the constrained side.
1 On a host you control
# every interface, port 8080, TLS on
wstunnel server wss://[::]:8080The server tells WebSocket from HTTP/2 on its own, so one listener serves both. Add --enable-webtransport to also serve QUIC on the same port. Pass your own certificate with --tls-certificate when you are ready.
2 On the constrained machine
# a SOCKS5 proxy on localhost:8888, exiting on the server
wstunnel client -L socks5://127.0.0.1:8888 \
wss://my.server.com:8080
# socks5h resolves names on the far side
curl -x socks5h://127.0.0.1:8888 https://example.comThe syntax mirrors ssh -L and ssh -R: what to listen on, then where to forward. Every tunnel flag can be repeated.
What it carries, and how
wstunnel started as a way to get SSH out of a locked-down office. It now moves most kinds of traffic in either direction.
Forward tunnels
Listen locally and forward a fixed destination through the server, the way ssh -L does. Read from a Unix socket or from stdin when a program wants a pipe rather than a port.
- tcp
- udp
- unix
- stdio
Dynamic proxies
Decide the destination per connection instead of up front. Run a SOCKS5 or HTTP proxy for browsers and tools, or a transparent proxy on Linux to route any program without configuring it.
- socks5
- http
- tproxy+tcp
- tproxy+udp
Reverse tunnels
Have the server listen and forward back to your machine, so a service behind NAT or a strict firewall becomes reachable from the server's network. Same schemes, flipped with -R.
- tcp
- udp
- socks5
- http
- unix
Transport
WebSocket by default, which performs best and survives most reverse proxies and CDNs. HTTP/2 is there for networks that block WebSocket upgrades. WebTransport runs the tunnel over HTTP/3 and QUIC for when TCP is throttled or lossy but UDP gets through. One server serves all three at once.
- ws / wss
- http / https
- webtransport
- IPv6
- proxy protocol v2
Getting out
Reach the server through an HTTP proxy with credentials, override or hide the TLS SNI, use Encrypted Client Hello, and resolve names over DNS-over-HTTPS or DNS-over-TLS so the local resolver never sees them.
- --http-proxy
- --tls-sni-override
- --tls-ech-enable
- --dns-resolver
Locking the server down
Accept only clients that know a secret path prefix, restrict which destinations may be reached, or write YAML rules matching path, authorization header, protocol, port, host and CIDR. Certificates, keys, client CAs and rule files all reload on change.
- --restrict-http-upgrade-path-prefix
- --restrict-to
- --restrict-config
- mTLS
Common setups
The commands people actually run. Each one assumes a server started with wstunnel server wss://[::]:443 unless shown otherwise.
# keep 5 connections to the server warm so new tunnels open instantly
wstunnel client -L socks5://127.0.0.1:8888 --connection-min-idle 5 wss://my.server.com:443
curl -x socks5h://127.0.0.1:8888 https://example.comIn Firefox, set a SOCKS v5 proxy at 127.0.0.1:8888 and tick Proxy DNS when using SOCKS v5 so names resolve on the server rather than your machine. Add ?login=admin&password=admin to the socks5 URL to require credentials.
ssh -o ProxyCommand="wstunnel client --log-lvl=off -L stdio://%h:%p wss://my.server.com:443" my-serverWith stdio:// as the source, wstunnel reads and writes on standard input and output, which is exactly what ssh expects from a ProxyCommand. Logging is switched off so nothing pollutes the stream.
# server: listen on 443 and only allow forwarding to the local ssh daemon
wstunnel server wss://[::]:443 --restrict-to 127.0.0.1:22
# client: go out through the corporate proxy, land on the server's sshd
wstunnel client -L tcp://9999:127.0.0.1:22 -p http://mycorporateproxy:8080 wss://my.server.com:443
ssh -p 9999 login@127.0.0.1The client speaks plain HTTPS to the proxy on port 443, negotiates TLS with your server through it, then forwards local port 9999 to the server's SSH daemon. --restrict-to keeps the server from being used to reach anything else.
# server
wstunnel server --restrict-to localhost:51820 wss://[::]:443
# client: forward UDP 51820 and never time the tunnel out
wstunnel client -L 'udp://51820:localhost:51820?timeout_sec=0' wss://my.server.com:443
# in wg0.conf point the peer at the local tunnel and lower the MTU
# Endpoint = localhost:51820
# MTU = 1400
# keep the wstunnel connection itself out of the VPN
sudo ip route add ip.of.my.server.com dev eth0 via 192.168.0.1
sudo wg-quick up wg0UDP tunnels close after 30 seconds of silence by default; timeout_sec=0 disables that. The static route stops WireGuard's catch-all AllowedIPs from swallowing wstunnel's own traffic. There is a fuller walkthrough in the docs.
# server listens on 8000 and forwards back to localhost:8000 on your machine
wstunnel client -R 'tcp://[::]:8000:localhost:8000' wss://my.server.com:443
# on your machine
python3 -m http.server
# from the server's network
curl http://localhost:8000Anything your laptop can reach becomes reachable from the server side, including services on a private LAN. Reverse tunnels are allowed by default; the server can forbid or scope them with a restriction file.
# needs root or CAP_NET_ADMIN
sudo wstunnel client -L 'tproxy+tcp://1080' -L 'tproxy+udp://1080' wss://my.server.com:443
# route a single command, or a whole shell, through it with cproxy
cproxy --port 1080 --mode tproxy -- curl https://example.com
cproxy --port 1080 --mode tproxy -- bashTransparent proxying means the program does not need to know about a proxy at all. cproxy sets up the iptables rules for you.
# the path prefix works as a shared secret; pick something long and random
wstunnel server --restrict-http-upgrade-path-prefix h3GywpDrP6gJEdZ6xbJbZZVFmvFZDCa4KcRd wss://[::]:443
wstunnel client --http-upgrade-path-prefix h3GywpDrP6gJEdZ6xbJbZZVFmvFZDCa4KcRd -L socks5://127.0.0.1:8888 wss://my.server.com:443Clients that do not present the prefix during the WebSocket upgrade are refused. For finer rules, or several secrets with different allowances, use --restrict-config with a YAML file; see restriction rules.
# server: also bind UDP on the same port and serve HTTP/3 over QUIC
wstunnel server --enable-webtransport wss://[::]:443
# the wts:// scheme is shorthand for the same thing
wstunnel server wts://[::]:443
# client: wts:// instead of wss://, everything else is unchanged
wstunnel client -L socks5://127.0.0.1:8888 wts://my.server.com:443Worth trying when TCP is throttled or lossy but UDP gets out, since QUIC avoids head-of-line blocking. UDP has to reach the server on that port: a firewall, security group or docker -p mapping that only forwards TCP makes the handshake time out after 10 seconds. TLS is always on, and --http-proxy cannot be used with it. Details in the docs.
Get wstunnel
Static binaries mean no runtime, no package manager, no dependencies. Copy it where you want it and run it.
Static binary
Pick your platform on the releases page, unpack, and put the binary on your PATH.
tar -xzf wstunnel_*_linux_amd64.tar.gz
chmod +x wstunnel
./wstunnel --helpDocker
Images are published to GitHub's container registry for every release.
docker pull ghcr.io/erebe/wstunnel:latestFrom source
Needs a Rust toolchain. Add --release for an optimized build.
cargo build --package wstunnel-cli
target/debug/wstunnel --help