CLI tool
erebe/wstunnel avatar
erebe/wstunnel

wstunnel: tunnelling TCP, UDP and SOCKS5 over WebSocket, HTTP2 or WebTransport

Tunnel all your traffic over Websocket or HTTP2 - Bypass firewalls/DPI - Static binary available

7,069 stars572 forksRustBSD-3-Clause

At a glance

What is it?
wstunnel is a Rust client and server that wrap arbitrary traffic in WebSocket, HTTP2 or WebTransport frames so it survives firewalls and proxies that only allow HTTP. It is a tunnelling primitive, not a VPN, and the README is explicit about where each transport breaks.
Who is it for?
Adopt wstunnel when you control both ends and the path in between only permits HTTP-shaped traffic: a static binary on each side, a -L or -R tunnel definition, and the demo server to confirm the path works before you commit. Do not adopt it as a general-purpose VPN replacement, and do not put an HTTP2 or WebTransport tunnel behind a reverse proxy or CDN, because the README states both configurations fail.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What wstunnel is for, and who ends up using it

The README starts from a plain observation: on a public network you are usually behind a firewall or proxy whose job is to restrict you to a subset of protocols, and HTTP is the one that is allowed almost everywhere. wstunnel takes whatever traffic you have and carries it inside the WebSocket protocol, which the README notes is compatible with HTTP, so the traffic looks like something the network already permits. That is the whole pitch. It is not a mesh network, not a key exchange system, and not a way to hide from your own endpoint.

The audience is narrower than the tagline suggests. You need a host you can reach from the restricted network and a host you can run the client on, and you need to be willing to define each tunnel by hand. The README lists what that buys you: static forward and reverse tunnelling over TCP, UDP, Unix sockets and stdio; dynamic tunnelling through a SOCKS5 proxy, an HTTP proxy or a transparent proxy; support for using an HTTP proxy as the gateway when you are behind one; proxy protocol support; TLS with certificate auto-reload; mTLS; IPv6. Reverse tunnelling is the part that changes the shape of a deployment. The client can expose a service that lives on the client side to whoever connects to the server, which is how you reach a machine that has no inbound reachability at all.

The project is a Rust rewrite. The README states that v7.0.0 was a complete rewrite and is not compatible with earlier versions, and that the previous Haskell code lives on a separate branch. The author gives the reason plainly: maintaining the Haskell ecosystem had become hard, and the rewrite also brought back an Armv7 build after a GHC release dropped support for it. If you are searching for an older wstunnel and find Haskell-era instructions, they do not apply.

How the client and server actually move bytes

There are two binaries and one connection between them. The server listens, the client dials out to it, and every tunnel you declare on the client becomes a stream multiplexed over that single connection. The address argument decides the transport: ws:// or wss:// for WebSocket, http:// or https:// for HTTP2, wts:// for WebTransport. The README recommends WebSocket if you are unsure, and the reasoning is in the warnings rather than the summary.

HTTP2 is where the design runs into the middleboxes it is meant to avoid. The client help text warns that a reverse proxy or CDN in front of the server will buffer the whole request before forwarding it, which obviously cannot work for a stream that never ends. It also warns that most reverse proxies, nginx being the named example, downgrade an HTTP2 request to HTTP1, and HTTP1 does not stream naturally. The stated conclusion is blunt: the only way HTTP2 works is with wstunnel directly exposed to the internet and no reverse proxy in front of it.

WebTransport carries its own set of constraints. It runs on HTTP/3 over QUIC, so UDP has to be reachable end to end on that port, and the README calls firewalls and container port mappings that only forward TCP the most common cause of a wts:// tunnel not connecting, with a 10 second handshake timeout as the symptom. The server must be started with --enable-webtransport, or with the wts:// scheme. TLS is always on because QUIC mandates TLS 1.3, and there is no cleartext variant. Three client options are unavailable with it: --http-proxy, --tls-sni-disable and --tls-ech-enable.

On the wire, each tunnel is described by a local-to-remote or remote-to-local specification. The client accepts -L and -R repeatedly, so a single client process can hold several tunnels at once. UDP tunnels carry a timeout_sec query parameter that force-closes the tunnel after a period of inactivity, defaulting to 30 seconds, with 0 disabling it. TCP tunnels can request a proxy protocol v2 header when the connection to the target is established. SOCKS5 listeners can require a login and password pair.

Installing wstunnel and running a first tunnel

There is no package manager step in the README. It points at the releases page for standalone binaries and describes them as something you copy where you want it, which is the intended install path on a machine where you have no build toolchain. The repository also contains a Dockerfile for a container image, built from a Rust builder stage and finished on debian:trixie-slim, with the binary at /home/app/wstunnel, SERVER_PROTOCOL defaulting to wss, SERVER_LISTEN to [::], SERVER_PORT to 8080, and port 8080 exposed.

Before building anything of your own, the README offers a demo server so you can confirm the path through your network works at all. The client listens locally on port 4443 and forwards to 10.43.0.11 on port 443 through wss://ws.erebe.dev, with the tunnel password given as demo and the TLS SNI overridden to google.fr:

bash
wstunnel client -L 'tcp://4443:10.43.0.11:443' -P demo --tls-sni-override=google.fr wss://ws.erebe.dev

In a second terminal, the README's curl against that local port should return the greeting Memento mori:

bash
curl -k https://localhost:4443

If that returns, the transport reaches the server and the failure is somewhere in your own tunnel definition. If it hangs, the problem is the path, not the tunnel, and the transport warnings above are the place to look. From there, a real tunnel follows the same shape. This one listens locally on TCP port 1212 and forwards to google.com on port 443:

bash
wstunnel client -L 'tcp://1212:google.com:443' wss://wstunnel.example.com

For a dynamic tunnel rather than a fixed one, the client can expose a SOCKS5 listener and resolve destinations on demand. The README's example binds it to the IPv6 loopback on port 1212:

bash
wstunnel client -L 'socks5://[::1]:1212' wss://wstunnel.example.com

A UDP tunnel looks the same but names the protocol, and can carry the inactivity timeout as a query parameter:

bash
wstunnel client -L 'udp://1212:1.1.1.1:53?timeout_sec=10' wss://wstunnel.example.com

Where wstunnel is the wrong tool

The first limitation is that wstunnel is not a VPN, despite the search traffic that assumes it is. There is no virtual interface, no routing table change, no address assignment. Every destination you want to reach has to appear in a -L or -R specification, or be resolved dynamically through the SOCKS5 or HTTP proxy listener. If your mental model is "connect and then everything routes through the tunnel", wstunnel does not do that, and you would be looking at the transparent proxy mode or a different class of software entirely.

The second is that the transport choice can silently disqualify itself. HTTP2 behind nginx does not work, and the README says so before you spend an afternoon on it. WebTransport needs UDP end to end, so a container platform that only publishes TCP ports will fail at the handshake. WebSocket is the safe default precisely because it has the fewest of these preconditions, and it is also described as the more performant of the three in the feature list, which makes the other two worth choosing only when you have a specific reason.

The third is compatibility. The rewrite at v7.0.0 broke the protocol, so a client and server on opposite sides of that line will not talk to each other. That matters for anyone running a long-lived server and updating clients piecemeal. The README does not document a rollback procedure for a version mismatch, and it does not describe a compatibility negotiation between versions, so the practical rule is to move both ends together.

Finally, the README is thin on operational detail. There is a benchmark section in the table of contents but this article does not cover its contents, so no throughput numbers should be assumed. mTLS is documented in a separate file, docs/using_mtls.md, rather than in the main README, which means the security configuration most people would want is one link away from the page they land on.

How wstunnel differs from a SOCKS proxy or a WireGuard tunnel

The closest comparison in the search data is Shadowsocks, and the difference is structural rather than a matter of tuning. Shadowsocks is a proxy protocol: a client speaks to a local SOCKS5 interface, the Shadowsocks process encrypts and forwards to a server that decrypts and connects onward. wstunnel also offers a SOCKS5 listener, and that is where the resemblance stops. The bytes between the wstunnel client and server are carried inside WebSocket, HTTP2 or WebTransport frames, which is what lets them pass equipment that has decided HTTP is acceptable. Shadowsocks traffic is not shaped like HTTP, which is exactly why it needs its own obfuscation story when DPI is aggressive.

WireGuard is a different layer again, and wstunnel's own topic list includes wireguard-tunnel, which is a hint about how the two are meant to combine rather than compete. WireGuard gives you an encrypted interface and a routing decision for every packet; wstunnel gives you a stream that survives a restrictive path. Running WireGuard's UDP inside a wstunnel UDP tunnel is the composition the topic list implies, and it is the reason someone would pick wstunnel over a plain proxy: the outer layer is the part that gets through.

Against a plain SOCKS5 proxy, the difference is reachability in the other direction. A SOCKS5 proxy is something you connect to. wstunnel's reverse mode is something that connects to you, which is what you need when the machine holding the service cannot accept inbound connections. That single capability is often the actual reason to use it.

Editorial conclusion

Adopt wstunnel when you control both ends and the path in between only permits HTTP-shaped traffic: a static binary on each side, a -L or -R tunnel definition, and the demo server to confirm the path works before you commit. Do not adopt it as a general-purpose VPN replacement, and do not put an HTTP2 or WebTransport tunnel behind a reverse proxy or CDN, because the README states both configurations fail. Verify first that the transport you picked can actually stream end to end: WebSocket if you are unsure, HTTP2 only with wstunnel directly exposed to the internet, and WebTransport only if UDP reaches the server port, since a TCP-only path makes the handshake time out after 10s.

Frequently asked questions

What is wstunnel?

It is a client and server that tunnel arbitrary traffic over WebSocket, HTTP2 or WebTransport, which the README describes as a way to bypass firewalls and proxies that only allow HTTP. It supports static forward and reverse tunnelling over TCP, UDP, Unix sockets and stdio, plus dynamic tunnelling through SOCKS5, HTTP proxy and transparent proxy modes.

What is the wstunnel protocol?

The client dials the server over one of three transports selected by the address scheme: ws:// or wss:// for WebSocket, http:// or https:// for HTTP2, and wts:// for WebTransport. Individual tunnels are declared with -L or -R specifications and multiplexed over that connection.

Is wstunnel safe?

The README does not make a security claim, so the answer depends on how you configure it. It supports TLS with certificate auto-reload, an embedded self-signed certificate or your own, and mTLS documented in docs/using_mtls.md. WebTransport always uses TLS because QUIC mandates TLS 1.3, while ws:// and http:// are cleartext.

wstunnel vs shadowsocks: what is the difference?

Shadowsocks is a proxy protocol whose traffic is not shaped like HTTP, while wstunnel carries its streams inside WebSocket, HTTP2 or WebTransport frames so they pass equipment that permits HTTP. wstunnel also offers a SOCKS5 listener, but the framing layer is the point of the project.

What are wstunnel alternatives?

The README does not list alternatives. The project's own topic list includes wireguard-tunnel, and the README notes the original inspiration was an npm package also called wstunnel, which the author rewrote in Rust to avoid installing Node.js.

Official sources

  1. erebe/wstunnel on GitHub
  2. Issues
  3. License: BSD-3-Clause
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/erebe-wstunnel.svg)](https://hysenlabs.com/projects/erebe-wstunnel)