UDP Tunnels for Game Servers and DNS: The Part of Tunneling Everyone Forgets
ngrok and Cloudflare Tunnel both punt on UDP. That's the entire reason rustunnel ships UDP support — game servers, DNS resolvers, QUIC, WebRTC media, and SIP all need it. A practical guide to UDP tunneling, the connection-tracking trick, and why nobody else bothers.
Almost every "tunnel" tool you've heard of skips UDP. ngrok? TCP only. Cloudflare Tunnel? HTTP / TCP. Tailscale Funnel? HTTP. Even frp's UDP support is the kind of thing the docs warn you about. The result: if you need a UDP tunnel to host a game server, expose a DNS resolver, run SIP signalling, or front a QUIC service — the standard tunnel toolkit has nothing for you.
rustunnel ships UDP from the same binary, with the same single command. This post is the complete guide to UDP tunneling with rustunnel: the connection-tracking trick, how to tunnel a game server from your laptop without touching the router, and every edge case you'll hit in production.
Status: The mechanism is shipped — see
docs/reference/udp-tunnelsfor the reference. This post covers the why and how it works.
Why UDP tunneling is hard
TCP tunnels are easy. The relay accepts a TCP connection, opens a TCP connection to the client over the control plane, and copies bytes between them. Connection state is the TCP socket itself; teardown is built into the protocol.
UDP has no connections. Each packet is independent. So:
- Who is this packet for? The relay receives a UDP packet on port
20000from203.0.113.5:38291. It needs to know which tunnel client should receive it. Source IP doesn't pin a flow — many clients sit behind the same NAT. - When does the "session" end? With TCP, FIN + RST. With UDP, never. So the relay has to time out idle flows, but if the timeout's too short the game-server "session" dies; too long, it leaks memory.
- Path MTU. TCP segments fragment cleanly via PMTU discovery. UDP packets that exceed the path MTU just drop, and the application has to handle it (or use a small fixed packet size).
The connection-tracking trick
rustunnel's UDP tunneling fakes a connection by tracking (source-IP, source-port) → tunnel-client mappings:
┌─────────────────────────┐
│ rustunnel-server │
│ │
external UDP │ ┌─────────────────┐ │ tunnel
client A │ │ flow table │ │
203.0.113.5 │ │ A → client X │ │ client X
:38291 ───▶│ │ B → client X │───▶│ :25565
│ │ C → client Y │ │
external UDP │ │ ... │ │
client B │ │ │ │ client Y
198.51.100.9 │ │ idle entries │ │ :53
:42018 ───▶│ │ expire after │───▶│
│ │ 60s │ │
│ └─────────────────┘ │
└─────────────────────────┘
When packet A from 203.0.113.5:38291 arrives:
- Look up
(203.0.113.5, 38291)in the flow table. - If a mapping exists → forward to the mapped tunnel client.
- If not → pick the tunnel client (usually only one for a given subdomain), record the mapping, forward.
- When the tunnel client sends a reply, look up which
(source-IP, source-port)it's destined for and emit it.
Idle entries expire after --udp-flow-timeout (default 60s). For game servers and DNS that's plenty; for bidirectional QUIC sessions in the multi-minute range, bump to --udp-flow-timeout 600s.
What you actually run
# Local DNS resolver on :5353
rustunnel udp 5353 \
--token rt_xxxxxxxxxxxxxxxxxxxxxxxx \
--remote-port 53
# → udp://eu.edge.rustunnel.com:53 → udp://localhost:5353Now dig @eu.edge.rustunnel.com -p 53 example.com resolves through your local Pi-hole. Useful for testing custom DNS responses without exposing your home IP.
# Minecraft Java edition on :25565
rustunnel udp 25565 \
--token rt_xxxxxxxxxxxxxxxxxxxxxxxx \
--remote-port 25565Friends connect to eu.edge.rustunnel.com:25565 and the relay forwards game packets to your laptop. Latency adds roughly 30–80 ms depending on the chosen edge region — fine for casual play, painful for competitive.
Real use cases
Hosting a Minecraft, Valheim, or Factorio server from your laptop
The standard playbook for sharing a local game server with friends is: log into your router, find the port-forwarding page, punch a hole for UDP:25565, note your external IP, and text it to everyone. Then the ISP rotates your IP and you start over. Or you're on a coffee-shop network. Or CG-NAT. It's always something.
With rustunnel, the workflow collapses to one command and works from any network:
# Minecraft Java (UDP — for the TCP query port see the companion post)
rustunnel udp 25565 \
--token rt_xxxxxxxxxxxxxxxxxxxxxxxx \
--remote-port 25565 \
--region eu
# Valheim (UDP 2456–2458 — requires a port range)
rustunnel udp 2456 \
--remote-port-range 2456-2458 \
--token rt_xxxxxxxxxxxxxxxxxxxxxxxx \
--region eu
# Factorio (UDP 34197 default)
rustunnel udp 34197 \
--token rt_xxxxxxxxxxxxxxxxxxxxxxxx \
--remote-port 34197Friends connect to eu.edge.rustunnel.com:25565. You never expose your home IP. The router is untouched.
Latency reality check: Minecraft survival plays fine up to 100–120 ms of total RTT. If you pick the edge region closest to your players (see Choosing an edge region), the relay typically adds 20–50 ms on top of the base internet path — imperceptible in survival and strategy, noticeable in PvP.
Testing a Pi-hole or Unbound config with real traffic
Pi-hole and Unbound both run a DNS resolver on port 53 (or 5353 when you don't want to run as root). Testing DNS behavior from a single machine is useful, but getting friends to point their resolvers at your box — and watching live query logs — is much more informative.
# Expose Pi-hole / Unbound on port 53 at the edge
rustunnel udp 5353 \
--token rt_xxxxxxxxxxxxxxxxxxxxxxxx \
--remote-port 53 \
--region euThen from any machine:
dig @eu.edge.rustunnel.com -p 53 example.com
dig @eu.edge.rustunnel.com -p 53 doubleclick.net # should be blocked by Pi-holeYour Pi-hole dashboard will show the queries arriving in real time. Tear down the tunnel when done; no firewall rules to clean up.
Running a SIP softphone gateway
Asterisk, FreeSWITCH, and Kamailio all depend on UDP. SIP signalling arrives on UDP/5060; media flows over UDP/RTP on a high port range negotiated in the SDP offer. That's two tunnel commands:
# SIP signalling
rustunnel udp 5060 \
--token rt_xxxxxxxxxxxxxxxxxxxxxxxx \
--remote-port 5060
# RTP media — match your Asterisk rtp.conf range
rustunnel udp 10000 \
--remote-port-range 10000-10100 \
--token rt_xxxxxxxxxxxxxxxxxxxxxxxxThis lets you test a softphone or SIP load-testing tool (SIPp, sipsak) against a local Asterisk instance without touching iptables or a router config. See Port-range tunnels for how the relay handles the RTP range.
Exposing a custom QUIC service
QUIC is UDP under the hood. HTTP/3 is the best-known QUIC application, but plenty of game engines, telemetry pipelines, and low-latency P2P systems speak raw QUIC for session management. If you're building one and want to expose it without touching firewall rules:
rustunnel udp 4433 \
--token rt_xxxxxxxxxxxxxxxxxxxxxxxx \
--remote-port 4433 \
--udp-flow-timeout 600sThe longer --udp-flow-timeout matters for QUIC: a connection can idle for several minutes without a packet while both endpoints consider themselves connected. The default 60 s timeout would silently expire the flow table entry, causing the next packet to look like a brand-new session to the relay.
QUIC's built-in PMTU probing handles the relay's framing overhead automatically — QUIC backs off its packet sizes until they pass through without fragmentation.
WebRTC TURN / coturn for staging environments
If you're shipping a video or audio product backed by WebRTC, you need a TURN server to relay media for clients that can't punch through symmetric NAT. The canonical TURN port is UDP/3478.
Spinning up a full coturn instance on a VPS for a staging environment is overhead. Running coturn locally and fronting it with rustunnel is not:
rustunnel udp 3478 \
--token rt_xxxxxxxxxxxxxxxxxxxxxxxx \
--remote-port 3478Point your ICE server config at turn:eu.edge.rustunnel.com:3478. Staging sessions get a real TURN relay without the infrastructure bill. Tear it down when the session ends.
Port-range tunnels (when one port isn't enough)
A single UDP port maps cleanly to one rustunnel udp command. But some protocols negotiate a range of ports per session:
- RTP (audio/video media): SIP SDP negotiation agrees on a base port and number of channels. A typical Asterisk install reserves 10000–20000; you only need the slice actually in use.
- BitTorrent: The DHT and peer wire protocol pick random high ports. A range tunnel covers the whole slice without opening one tunnel per peer.
- Game protocols: Source Engine games (TF2, CS:GO) use UDP/27015 for game traffic and open auxiliary ports for HLTV, TV relay, and RCON. A small range covers all of them.
To expose a contiguous range at the edge:
rustunnel udp 10000 \
--remote-port-range 10000-10100 \
--token rt_xxxxxxxxxxxxxxxxxxxxxxxxThis maps edge:10000 through edge:10100 to localhost:10000 through localhost:10100. The relay's flow table treats each external port as its own independent slot — packets arriving on port 10043 are tracked and timed out separately from packets on port 10044, even if they come from the same source IP. This is exactly what you want for RTP: each media stream sits in its own slot and expires independently.
Billing: port-range tunnels bill identically to single-port tunnels — per gigabyte of data through the relay, no per-port charge. See Billing and usage for the rate card.
Port count: the maximum range width per tunnel command is 1000 ports. If you need a wider range — say, the full Asterisk RTP pool — split into multiple commands.
Caveats
Latency doubles at the relay
Every UDP packet travels from the sender to the relay, then from the relay to your local machine (or vice versa). Every send/receive round trip adds 2x the relay RTT compared to a direct path. If the relay is 40 ms from the sender and 10 ms from your machine, each round trip costs an extra 100 ms that wasn't there before.
The fix is simple: pick the edge region nearest to your players, not nearest to you. See Choosing an edge region — the selector shows ping estimates from major cities.
For reference: a relay-added RTT of under 30 ms is imperceptible in most game genres. Under 80 ms is playable for survival and strategy. Above 150 ms is rough for anything real-time.
Framing overhead and MTU
rustunnel adds roughly 24 bytes of routing metadata to every UDP packet it forwards. For most payloads this is invisible — a standard Ethernet MTU is 1500 bytes and a typical game packet is well under 1000 bytes.
Where it bites: if your application already generates near-MTU packets (some video codecs, some custom protocols), the extra 24 bytes pushes the packet over the MTU and it either fragments (slow) or gets dropped (bad). The safe upper bound for application payload is around 1460 bytes through the relay.
QUIC handles this natively via PMTU probing. Raw RTP defaults to small payloads (typically 160–1400 bytes depending on codec) and usually fits. If you control the protocol, cap UDP payloads at 1400 bytes and you'll never see fragmentation through the relay.
Players see the relay IP, not yours
That's the point — you never expose your home address. But some legacy protocols embed the sender's IP address in the application payload. The relay cannot rewrite those embedded addresses; the remote side gets an address that either doesn't route back to it (the relay's internal address) or routes to the relay rather than your machine.
If you hit this: check your protocol's documentation for NAT traversal notes. Many game SDKs have a "behind NAT" or "relay mode" flag that disables embedded-IP tricks. SIP implementations that embed IP in SDP often expose an externip config option for exactly this scenario.
How this compares
| Tool | UDP support | Port range | DNS friendly |
|---|---|---|---|
| ngrok | no | n/a | n/a |
| Cloudflare Tunnel | no (HTTP / TCP only) | n/a | n/a |
| Tailscale Funnel | no | n/a | n/a |
| frp | yes | yes | yes |
| rustunnel | yes | yes | yes |
Next steps
- Read the UDP reference — full flag list and TOML config.
- Self-host the relay near your players — every 1 ms of relay-RTT shows up in game ping.
- Read the P2P guide — for many UDP use cases (game sessions, large file transfer), P2P is a better fit than relay.