VPNSmith
tunneling-obfuscationINFO

wstunnel: TCP/UDP over WebSocket in 2026 (complete guide)

wstunnel wraps WireGuard, SSH, or any TCP/UDP traffic in WebSockets on port 443. Contabo install, real-world latency, corporate firewall comparison.

By Eric Gerard · Fondateur · VPNSmith — Spécialiste self-host VPN & VPS GDPR9 min readPhoto via Unsplash

Affiliate disclosure — This article contains Contabo affiliate links. If you order a VPS via our links, we earn a commission at no extra cost to you. We only write up what we actually run in production on our own VPS.

You know the story: your WireGuard VPN runs flawlessly at home, you go on-site at a client or sit in a coworking open space, and UDP port 51820 is blocked cold. The corporate Wi-Fi lets through 80, 443, sometimes 22, and that's it. You log in via the captive portal, your tunnel won't come up, and you're locked out of your own infrastructure.

That's the exact scenario wstunnel solves. The idea is simple: wrap any TCP or UDP traffic in WebSockets on port 443 — meaning, make your WireGuard (UDP) look like legitimate HTTPS WebSocket traffic. The firewall sees wss://cdn.yourdomain.com on port 443, lets it through, and on the other side wstunnel unwraps to hand the UDP packets back to WireGuard.

This guide covers installing wstunnel on a Contabo VPS (Ubuntu 24.04), client config on Linux/Windows/macOS/Android, real latency and throughput benchmarks, and when wstunnel is the right call vs V2Ray or Cloak.

Why wstunnel works where plain WireGuard fails

Three families of blocks kill a "vanilla" WireGuard tunnel:

  • Classic corporate firewall: restrictive egress allowlist, only 80/443/53 leave. UDP port 51820 has no chance.
  • Hotel / airport captive portals: before auth, everything except 80/443 TCP is dropped. And even after login, some keep UDP closed.
  • Advanced national / corporate DPI: sees the WireGuard handshake, identifies the protocol, and drops. The listening port doesn't matter.

WebSocket on 443 bypasses the first two because it IS HTTPS — an upgrade from HTTP/1.1, accepted by every TLS proxy. For national DPI (GFW, SmartFilter), you need to pair it with real TLS and a valid cert; wstunnel handles that via wss://.

The wstunnel tool is written in Rust by Erebe (GitHub repo). It's a single binary, ~7 MB, that acts as both server and client. No Python to patch, no Node.js to maintain, no mandatory Docker. You copy the binary, run a command, tunneling starts.

Server install on a Contabo VPS

We use a Contabo VPS S (4 vCPU, 8 GB RAM, €4.99/month) running Ubuntu 24.04 LTS. If you don't have a VPS yet, check the Contabo VPS S 24-month deal. You can share this VPS with your existing WireGuard — wstunnel uses under 50 MB of RAM idle.

Step 1 — Grab the wstunnel binary

WSTUNNEL_VER="10.1.4"  # check the latest on github.com/erebe/wstunnel/releases
curl -L -o /usr/local/bin/wstunnel \
  "https://github.com/erebe/wstunnel/releases/download/v${WSTUNNEL_VER}/wstunnel_${WSTUNNEL_VER}_linux_amd64"
chmod +x /usr/local/bin/wstunnel
wstunnel --version

Step 2 — Domain + Caddy reverse proxy (recommended)

For a believable wss://, we put Caddy in front to handle Let's Encrypt TLS automatically. Prerequisite: a domain pointing to the VPS IP (A record cdn.yourdomain.com → 188.245.x.x).

# Caddy install
apt update && apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
apt update && apt install -y caddy

/etc/caddy/Caddyfile:

cdn.yourdomain.com {
  root * /var/www/html
  file_server

  @ws {
    path /ws/*
    header Connection *Upgrade*
    header Upgrade websocket
  }
  reverse_proxy @ws 127.0.0.1:8080
}

The @ws block matches only WebSocket upgrade requests on the /ws/* path. Every other request sees a normal static HTML site (drop a dummy blog or maintenance page in /var/www/html).

Step 3 — wstunnel systemd service

/etc/systemd/system/wstunnel.service:

[Unit]
Description=wstunnel server
After=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/wstunnel server ws://127.0.0.1:8080 \
  --restrict-to 127.0.0.1:51820
Restart=always
RestartSec=5
User=nobody

[Install]
WantedBy=multi-user.target

The --restrict-to flag forbids clients from using wstunnel as an open proxy: only 127.0.0.1:51820 (our WireGuard) will be allowed. Critical for security.

systemctl daemon-reload
systemctl enable --now caddy wstunnel
journalctl -u wstunnel -f  # verify it's listening

At this stage your server exposes https://cdn.yourdomain.com (dummy HTML page) and a WS tunnel at wss://cdn.yourdomain.com/ws/*. No exotic UDP port exposed externally — to a firewall, it's a normal HTTPS site.

Client config: Linux, Windows, macOS, Android

The wstunnel client binary is the same as the server. You download the right release for your OS and run a different command.

Linux / macOS:

wstunnel client \
  --local-to-remote 'udp://51820:127.0.0.1:51820' \
  wss://cdn.yourdomain.com/ws

This opens a local UDP listener on 127.0.0.1:51820 and tunnels everything that lands there toward 127.0.0.1:51820 on the server via WebSocket on port 443. You then point your local WireGuard config at Endpoint = 127.0.0.1:51820 instead of the public VPS IP.

Example WireGuard client config (/etc/wireguard/wg0-via-ws.conf):

[Interface]
PrivateKey = YOUR_CLIENT_PRIVATE_KEY
Address = 10.7.0.2/24
DNS = 1.1.1.1

[Peer]
PublicKey = SERVER_PUBLIC_KEY
AllowedIPs = 0.0.0.0/0
Endpoint = 127.0.0.1:51820   # local — wstunnel relays
PersistentKeepalive = 25

Launch in this order:

wstunnel client --local-to-remote 'udp://51820:127.0.0.1:51820' wss://cdn.yourdomain.com/ws &
wg-quick up wg0-via-ws

Windows: grab wstunnel.exe from the releases page, launch from an admin PowerShell. To automate, create a wstunnel-client scheduled task starting at logon. The official WireGuard app for Windows accepts Endpoint = 127.0.0.1:51820 without complaining.

Android: no native ARM binary is officially distributed, but Termux lets you compile or fetch the ARM64 build (wstunnel_*_linux_arm64). Combined with WireGuard Android and the "Excluded apps" feature to exclude Termux's PID, it works. It's the least turnkey option — on mobile, V2Ray Android is simpler if you don't specifically need WireGuard.

Real-world benchmarks: latency and throughput

iperf3 + ICMP ping measurements, residential gigabit fiber client in Paris, Contabo VPS S Nuremberg, late March 2026. Median over 10 sessions.

SetupICMP latencyiperf3 TCP throughputServer CPU @ 100 Mbps
Plain WireGuard (UDP/51820)18 ms195 Mbps4%
wstunnel ws:// (no TLS)24 ms145 Mbps9%
wstunnel wss:// + Caddy31 ms112 Mbps14%
wstunnel wss:// + Cloudflare proxy38 ms88 Mbps14%

Reading: added latency hovers around 10–13 ms when TLS terminates locally, 20 ms once Cloudflare sits in front. Throughput drops 40% between plain WireGuard and wstunnel wss:// — the price you pay to cross a corporate firewall or DPI.

For web browsing, Zoom/Meet 720p video, and HD Netflix: no perceptible difference. For 4K, competitive gaming, or large transfers, the WS tunnel is suboptimal — fall back to plain WireGuard when possible.

Typical use cases

1. Remote employee behind a strict corporate firewall. Classic setup: personal Contabo VPS + wstunnel + WireGuard to rejoin your home network (NAS, Home Assistant, dev box). The corp firewall sees HTTPS toward a personal domain, doesn't block. Compatible with reasonable BYOD policies.

2. Digital nomad with unpredictable hotel Wi-Fi. wstunnel is your plan B when WireGuard won't egress. Keep a "direct WireGuard" profile for the good networks, a "via wstunnel" profile for the bad ones. Manual switch in 5 seconds.

3. CTF / pentest / homelab. When you want to expose an internal service (RDP Windows lab, dev MySQL, SSH bastion) on port 443 of a public VPS without touching the application reverse proxy. wstunnel TCP is simpler than SSH tunneling for less technical teammates.

4. Progressive migration from a consumer VPN to self-host. If you still use a commercial VPN and want to test self-hosting while keeping a fallback: stand up your Contabo + WireGuard + wstunnel stack, and keep your NordVPN subscription active for a month as a backup. That's what we did ourselves in 2024 — we cut Nord after 6 weeks once self-host was stable.

Hardening the deployment

Strong client authentication. By default, anyone who knows the wss://cdn.yourdomain.com/ws URL can open a tunnel. Enable the --http-upgrade-path-prefix option with a 32-character random secret:

# Server side
wstunnel server ws://127.0.0.1:8080 --http-upgrade-path-prefix /secret-abc123xyz

# Client side
wstunnel client --http-upgrade-path-prefix /secret-abc123xyz ...

Without the right prefix, the server returns 404. Combined with fail2ban on Caddy logs, this blocks fast scanners.

Explicitly restrict destinations. The --restrict-to 127.0.0.1:51820 only allows tunneling to the local WireGuard. Don't omit it — otherwise your VPS becomes an open proxy that bots will abuse.

fail2ban on Caddy 404s. Add a jail that bans an IP after 10 404 responses in 60 seconds:

# /etc/fail2ban/jail.d/caddy-404.conf
[caddy-404]
enabled = true
port = http,https
filter = caddy-404
logpath = /var/log/caddy/access.log
maxretry = 10
findtime = 60
bantime = 3600

The caddy-404.conf filter matches JSON lines with "status":404 — see Caddy logging docs for the exact schema.

Cert renewal. Caddy handles Let's Encrypt automatically, nothing to do. Check every 60 days with caddy adapt that the config still validates.

wstunnel vs alternatives

CriterionwstunnelV2Ray + WS+TLSCloaksshuttle
Install time10 min45 min20 min5 min
Multi-protocolTCP + UDPTCP (UDP via plugin)TCP + UDPTCP only
Multi-userNot nativeYesYesNo
China GFW bypassAverageExcellentGoodWeak
Corporate firewall bypassExcellentExcellentExcellentGood
MaintenanceLowMediumLowNear zero

Pragmatic verdict:

  • Solo / 1–3 users / corporate or light DPI bypass → wstunnel.
  • Multi-user / China GFW bypass → V2Ray (see our V2Ray VMess/VLess guide).
  • You want to add a TLS layer on top of an existing WireGuard without redoing everything → Cloak.
  • Just escape a corp network for SSH / web → sshuttle (no VPS required).

Common troubleshooting

Symptom: wss handshake failed: 502 bad gateway. The wstunnel server isn't listening on 8080, or Caddy can't find it. Check systemctl status wstunnel and ss -tlnp | grep 8080.

Symptom: tunnel comes up but no traffic flows. The server-side WireGuard doesn't authorize the client peer. Re-check wg show and the server [Peer] block — usually a copy-paste error in AllowedIPs.

Symptom: abnormally low throughput (< 30 Mbps). Cloudflare proxy enabled (orange cloud): Cloudflare throttles WebSockets on free plan. Disable DNS proxy (grey cloud) or move to a domain without Cloudflare.

Symptom: disconnections every 10 minutes. Caddy has a default timeout on long WS sessions. Add inside the reverse_proxy block:

reverse_proxy @ws 127.0.0.1:8080 {
  transport http {
    keepalive 30s
    keepalive_idle_conns 100
  }
}

Further reading

Sources and references:


Published 2026-06-03. Tests run on a Contabo VPS S Nuremberg + residential fiber client in Paris, March 2026. Performance can vary noticeably depending on the chosen Contabo datacenter, client ISP, and peering quality — always benchmark in your own context before committing to a setup.

Reminder: wstunnel and VPN self-hosting are perfectly legal in the EU, US, Canada, and most democratic countries. Check local regulations in China, Iran, UAE, Russia before deploying — VPNSmith publishes this content for educational purposes.

★ Datacenter Nuremberg GDPR · ✓ IPv4 dédiée incluse · 200+ Mbps garantis

Get Contabo30 jours satisfait ou remboursé