Docs
Quickstart
The Wormhop agent runs next to your service and connects out to Wormhop's edge, so you don't need to open any inbound ports. This guide takes you from nothing to a public URL.
Install
On macOS or Linux:
$curl -fsSL https://github.com/jason-shen/wormhop-releases/releases/latest/download/install.sh | shWindows builds are on the releases page.
Authenticate
Create an account, then open the dashboard and create an authtoken. It's shown once, so copy it straight into the agent:
wormhop authtoken pw_xxxxxxxx --server tunnel.wormhop.dev
HTTP tunnels
wormhop http 3000 # random subdomain wormhop http 3000 --subdomain my-app # reserved subdomain (Hobbyist and up) wormhop http 8080 --hostname app.example.com --host-header rewrite --basic-auth me:secret
Every HTTP tunnel is also served over HTTPS with a trusted certificate. WebSockets work, and Wormhop adds X-Forwarded-For, X-Forwarded-Proto and X-Forwarded-Host. Use --host-header rewrite for dev servers such as Vite that reject unknown Host headers.
TCP and UDP
wormhop tcp 22 --remote-port 20022 # reserved TCP port wormhop udp 27015 # random UDP port wormhop udp 30000-30099 --remote-port 30000 # a range of 100 UDP ports
Reserve ports in the dashboard under TCP & UDP ports. A range counts each port toward your plan's limit.
Config file
Define several tunnels once and start them together with wormhop start --all. Run wormhop config to see where the file lives.
Custom domains
- Add the domain in the dashboard under Domains.
- Create a
CNAMEtoedge.wormhop.devand theTXTrecord_wormhop-challenge.<your host>the dashboard shows you. - Click verify. Wormhop issues a Let's Encrypt certificate and renews it automatically.
- Start a tunnel with
--hostname home.example.com.
Restrictive networks
The agent normally connects on port 7000. On networks that block it, the agent connects through port 443 instead without printing anything. It tries both ports at once after a short head start for 7000, so a blocked port doesn't cost a timeout. Run with --log debug to see which port it used.
WebRTC and SFUs
Signalling goes over an HTTP tunnel, media over a UDP tunnel. Use the same port numbers locally and publicly, because SFUs advertise their own port in ICE candidates, and set the SFU's external IP to the Wormhop edge address.
| SFU | Single port | Advertise server IP |
|---|---|---|
| Pion | SetICEUDPMux | SetNAT1To1IPs, SetLite(true) |
| LiveKit | rtc.udp_port | rtc.node_ip |
| mediasoup | a WebRtcServer | announcedAddress |
| Janus | rtp_port_range | nat_1_1_mapping |
More in the WebRTC guide.
Performance
Measured on a 2-vCPU cloud VM with release builds. The server, agent, test client and an echo service all shared the same two cores over loopback, with TLS on the agent connection.
| Measurement | Server | Agent |
|---|---|---|
| Memory at idle | 11 MB | 6 MB |
| Memory with 1,000 open connections through one tunnel | 29 MB | 25 MB |
| CPU per GB relayed, including TLS | 2.2 s | 2.0 s |
- Throughput: 161 MB/s in each direction at once through one TCP tunnel (about 1.3 Gbit/s), limited by the two shared cores.
- Latency added per HTTP request: 0.19 ms median.
- WebRTC under loss: with 5% loss injected on the agent's link, 0.27% of RTP was lost after WebRTC's own recovery, and the median round trip stayed at 1.3 ms.