> ## Documentation Index
> Fetch the complete documentation index at: https://rustunnel.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# UDP Tunnels in rustunnel — Rust-based UDP forwarding for game servers, DNS, and QUIC

> rustunnel UDP tunnels forward datagrams from a public port on the tunnel server to a local UDP service — game servers, DNS resolvers, QUIC endpoints, VoIP. Covers sessions, length-prefixed framing, server configuration, and CLI usage.

This page documents **UDP tunnels in rustunnel**, our Rust-based UDP forwarding implementation. A UDP tunnel exposes a local UDP service — a game server, DNS resolver, QUIC endpoint, VoIP service — on a public port on the rustunnel server, forwarding datagrams bidirectionally between remote clients and the local service over the same multiplexed control connection that powers HTTP and TCP tunnels. For TCP-style direct connections see [P2P tunnels](/reference/p2p-tunnels); for HTTP load-balanced groups see [Load Balancing & Health Checks](/reference/load-balancing).

## 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.

```text theme={null}
Remote client -> server:20100 (UDP) --tunnel--> Client -> localhost:27015 (UDP) -> Service
```

* **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.

<Info>
  Unlike HTTP/TCP tunnels, there is no long-lived connection to track. Sessions are created on first datagram and expire on inactivity.
</Info>

***

## Connection Flow

<Steps>
  <Step title="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 }`.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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`).
  </Step>

  <Step title="Bidirectional datagram forwarding">
    The server forwards the buffered and subsequent datagrams into the yamux stream. The client writes them to the local service and copies replies back through the stream. The server delivers replies to the original source address.
  </Step>
</Steps>

Subsequent datagrams from the same remote source reuse the existing session and yamux stream. Datagrams from a new source create a new session.

***

## 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:

```text theme={null}
[ 4-byte u32 big-endian length ][ N-byte payload ]
```

* 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.

<Tip>
  UDP tunnels reuse the client's existing WebSocket connection to the server. No new sockets are opened from client to server for UDP.
</Tip>

***

## 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 |

A reaper task runs every 10 seconds and closes any session idle for more than **60 seconds**. Because UDP has no FIN, this is how rustunnel decides when a session has ended.

***

## Server Configuration

UDP tunnels require a dedicated port range in `server.toml`. It must not overlap with `tcp_port_range`, and the ports must be reachable through the host firewall.

```toml theme={null}
[limits]
# Inclusive [low, high] port range reserved for UDP tunnels.
# Each active UDP tunnel consumes one port from this range.
# Set to [0, 0] to disable UDP tunnels entirely.
udp_port_range = [20100, 20199]
```

Open the range in the host firewall:

```bash theme={null}
ufw allow 20100:20199/udp comment "rustunnel UDP tunnels"
```

<Warning>
  `udp_port_range` and `tcp_port_range` must not overlap. Setting `udp_port_range = [0, 0]` disables UDP tunnel registration entirely — any client attempting to register a UDP tunnel will get an error.
</Warning>

***

## CLI Usage

Expose a local UDP service:

```bash theme={null}
# Expose a game server on udp/27015
rustunnel udp 27015 --token YOUR_TOKEN

# Pin to a specific region
rustunnel udp 27015 --region eu --token YOUR_TOKEN

# Local development against a self-hosted server
rustunnel udp 53 --server localhost:4040 --insecure --token dev-secret-change-me
```

On success the client prints the allocated public UDP endpoint:

```text theme={null}
UDP tunnel ready: udp://eu.edge.rustunnel.com:20100 -> localhost:27015
```

Any datagram sent to that public `host:port` is forwarded to your local service, and replies are returned to the original sender.

***

## Config File

In `~/.rustunnel/config.yml`:

```yaml theme={null}
tunnels:
  gameserver:
    proto: udp
    local_port: 27015

  dns:
    proto: udp
    local_port: 53
```

Start all configured tunnels with `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

<CardGroup cols={2}>
  <Card title="P2P tunnels" icon="network" href="/reference/p2p-tunnels">
    Connect two clients directly with NAT hole punching over QUIC, with automatic relay fallback.
  </Card>

  <Card title="Load balancing and health checks" icon="route" href="/reference/load-balancing">
    Run multiple backends behind one subdomain or TCP port with automatic failover.
  </Card>

  <Card title="Client guide" icon="terminal" href="/guides/client-guide">
    Every command, flag, and config option for the rustunnel CLI client.
  </Card>

  <Card title="Self-hosting" icon="server" href="/guides/self-hosting">
    Run your own rustunnel server and configure port ranges, TLS, and firewall rules.
  </Card>
</CardGroup>
