Concepts
UDP is connectionless, so there is no persistent “connection” to forward. Instead, rustunnel tracks sessions — one per unique remote source address (IP:port) — and forwards datagrams in both directions for as long as the session is active.
- Public port — allocated from a dedicated UDP port range on the server (separate from the TCP tunnel pool).
- Session — identified by the remote client’s source
IP:port. Each unique source gets its own yamux stream to the client and its own locally bound UDP socket. - Idle timeout — sessions are reaped after 60 seconds of inactivity.
Unlike HTTP/TCP tunnels, there is no long-lived connection to track. Sessions are created on first datagram and expire on inactivity.
Connection Flow
Client registers a UDP tunnel
The rustunnel client sends
RegisterTunnel with protocol = Udp. The server allocates a port from udp_port_range and replies with TunnelRegistered { public_port }.Remote client sends a datagram
A remote UDP client sends a datagram to the allocated public port. The server’s UDP edge looks up a session keyed by the datagram’s source address.
Server creates a session on first datagram
If no session exists, the server creates one, buffers the datagram (up to 64 packets), and sends
NewConnection { conn_id, protocol: Udp } to the client over the control-plane WebSocket.Client opens a yamux stream
The client opens a new yamux stream for the
conn_id, binds a local UDP socket, and connect()s it to the local service address (e.g., 127.0.0.1:27015).Transport and Framing
UDP datagrams ride over the same yamux-over-WebSocket tunnel used by HTTP and TCP. Because yamux is a reliable byte stream and UDP is message-oriented, rustunnel preserves datagram boundaries with a simple length-prefixed frame:- No encoding overhead beyond the 4-byte header.
- Maximum payload is bounded by UDP itself (65,507 bytes after the IP/UDP header).
- Framing overhead is not counted toward
bytes_proxied— only raw payload bytes are metered.
Sessions
A UDP session holds:| Field | Purpose |
|---|---|
| Forwarding channel | Edge listener pushes datagrams into the yamux stream |
| Last-activity timestamp | Used by the reaper to detect idle sessions |
| Initial buffer (up to 64 datagrams) | Holds packets that arrive while the client is opening the yamux stream |
Server Configuration
UDP tunnels require a dedicated port range inserver.toml. It must not overlap with tcp_port_range, and the ports must be reachable through the host firewall.
CLI Usage
Expose a local UDP service:host:port is forwarded to your local service, and replies are returned to the original sender.
Config File
In~/.rustunnel/config.yml:
rustunnel start.
Limitations and Notes
| Topic | Notes |
|---|---|
| Datagram size | Up to 65,507 bytes. Payloads exceeding the path MTU will fragment at the IP layer; rustunnel does not perform application-level segmentation. |
| Per-source sessions | Each unique source_ip:source_port creates its own session. NATs that rewrite ports aggressively (some mobile carriers) can produce many short sessions. |
| Idle reaping | 60-second inactivity timeout. Services that rely on very infrequent keepalives may need to send periodic traffic. |
| Rate limiting | Per-tunnel rate limits apply; each datagram counts as one request against rate_limit_rps. |
| Metering | bytes_proxied counts raw UDP payload only; the 4-byte framing header is excluded. |
| No TLS | UDP is forwarded as-is. For encryption, use DTLS, QUIC, or WireGuard inside the tunnel. |
Troubleshooting
| Symptom | Likely cause |
|---|---|
udp_port_range is disabled on registration | udp_port_range = [0, 0] in server.toml. Set a real range. |
| Datagrams reach the server but not the local service | Local firewall blocking the loopback UDP port, or the service isn’t actually listening on UDP (ss -ulnp to confirm). |
| Replies from the local service are never seen remotely | The service isn’t replying to the packet’s source — make sure it replies to the datagram source, not a hard-coded peer. |
| New session every few packets from the same client | Source NAT rewriting the client’s port — a client-network issue, often unavoidable on mobile. |
| Connection “drops” after ~60 seconds of idle | Idle reaper closed the session. Send a keepalive or reopen on demand. |
| Public UDP port not reachable from the internet | Host firewall isn’t open for udp_port_range. Run ufw allow 20100:20199/udp. |
Related pages
P2P tunnels
Connect two clients directly with NAT hole punching over QUIC, with automatic relay fallback.
Load balancing and health checks
Run multiple backends behind one subdomain or TCP port with automatic failover.
Client guide
Every command, flag, and config option for the rustunnel CLI client.
Self-hosting
Run your own rustunnel server and configure port ranges, TLS, and firewall rules.

