Skip to content

RemoteToHome-io/wg-udp-relay

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

42 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

WireGuard UDP Relay

A lightweight, high-performance UDP relay server designed for WireGuard VPN tunnels used in self-hosted VPN setups. This tool is specifically designed to help with personal (often home-based) VPN router setups that may have a VPN server hosted on a residential ISP with a dynamic home IP address (e.g. self-hosted VPNs using GL.iNet routers). It helps route WireGuard packets through intermediate servers, useful for bypassing restrictive networks or optimizing routing paths, especially from travel countries that may have poor international connection paths to your VPN server host country.

Important security note:

This type of relay does not have the ability to decrypt or modify the WireGuard VPN tunnel traffic (it does not have access to the client/server decryption keys). It simply allows you to "bounce" the tunnel via a public server to assist with international routing performance or bypass endpoint restrictions. This relay also features the ability to listen on multiple ports, so you could send your VPN client traffic on ports such as 443/UDP to make it appear more like HTTPS/3 QUIC UDP traffic, which can help bypass port restrictions and some types of throttling on a remote travel network.

Transparency:

The project code went from hand-written, to AI refactored and enhanced with manual review. Given it has no access to private data or any portion of the tunnel data, the priority is quick development/enhancement and documentation.

To-do / coming soon:

  • Anycast routing - explore adding Anycast support with Vultr or DigitalOcean
  • GeoDNS - Route clients to nearest relay based on location
  • Load balancing - Multiple relays behind DNS round-robin

Features

  • High-performance UDP packet relaying
  • Multiple port support - listen on multiple ports simultaneously
  • Automatic DDNS monitoring - detects IP changes and updates routing
  • Environment variable configuration via .env file
  • Docker Compose for easy deployment
  • Minimal overhead and latency
  • Connection tracking and timeout management
  • Graceful session migration on endpoint IP changes
  • IPv4 and IPv6 support
  • Host network mode for full port access

Use Cases

  • Route WireGuard traffic through intermediate servers
  • Bypass network restrictions that block direct VPN connections
  • Optimize routing paths for better performance
  • Support multiple WireGuard clients on different ports

Why Not Just Use socat?

A simple socat one-liner can relay UDP packets:

socat -T15 udp-recvfrom:34666,reuseaddr,fork udp-sendto:203.0.113.10:34666

If your WireGuard server has a static IP, this works fine and you may not need this project. However, wg-udp-relay was built for a harder problem: home-based WireGuard servers on residential ISPs with dynamic IPs.

Capability socat wg-udp-relay
Basic UDP relay Yes Yes
DDNS hostname monitoring No — resolves once at start, breaks when IP changes Yes — periodic re-resolution with configurable interval
Session migration on IP change No — active tunnels die Yes — seamless migration to new IP
Multi-port listening One port per instance Multiple ports in a single process (e.g., 51820 + 443)
Port 443/UDP (QUIC mimicry) Requires separate instance Built-in via comma-separated port list
Connection tracking Fork per connection (OS process) Goroutines with session state
Docker deployment Manual setup Docker Compose with .env configuration
Configuration Command-line only Environment variables + command-line flags

Use socat when: Your WireGuard server has a static IP and you only need one listen port. It's simpler and already installed on most Linux systems.

Use wg-udp-relay when: Your WireGuard server is behind a residential ISP with a dynamic IP (DDNS), you want multi-port listening to bypass port restrictions, or you need unattended operation that survives endpoint IP changes.

Hardware Requirements

Budget Option (Good Performance)

Nanode 1GB ($5/month):

  • 1 shared vCPU, 1GB RAM
  • 40 Gbps in / 1 Gbps out
  • Throughput: 200-250 Mbps with tuning
  • Best for: Budget-conscious users, light-moderate use
  • Value: +++++

Recommended (Best Balance)

Linode 2GB ($12/month):

  • 1 shared vCPU, 2GB RAM
  • 40 Gbps in / 2 Gbps out
  • Throughput: 300-350 Mbps with tuning
  • Best for: Most production deployments
  • Value: ++++

Premium (Guaranteed Performance)

Dedicated CPU Plans (starting $36/month):

  • 2+ dedicated vCPUs, 4GB+ RAM
  • 40 Gbps in / 8+ Gbps out
  • Throughput: 350+ Mbps with tuning
  • Best for: Enterprise, SLA requirements, many simultaneous users
  • Value: ++

Real-World Testing Results

Puerto Vallarta, Mexico → California Relay → Singapore Server

Nanode 1GB ($5/mo):

  • Average: 215 Mbps down / 125 Mbps up
  • Range: 178-242 Mbps down / 80-147 Mbps up
  • Latency: 248ms
  • Notes: Variable upload during peak times

Linode 2GB ($12/mo):

  • Average: 332 Mbps down / 160 Mbps up
  • Latency: 247ms
  • Notes: Consistent, stable performance

Comparison vs Direct Connection:

  • Direct PV→SG: 90 Mbps / 65 Mbps
  • Nanode relay: 2.4x faster download
  • Linode 2GB relay: 3.7x faster download

Recommendation

  • Start here: Linode 2GB ($12/mo) - best balance of cost and performance
  • Budget option: Nanode 1GB ($5/mo) - still 2x+ faster than direct connection
  • Skip: Dedicated CPU plans unless you need guaranteed >350 Mbps or serve 20+ users

Critical: Buffer tuning (10 minutes, free) is required regardless of plan. An untuned $36 dedicated instance performs worse than a tuned $5 Nanode.

Geographic Location

Choose a VPS location between your travel destinations and home server for optimal routing performance.

Quick Start with Docker Compose

  1. Clone the repository:
git clone https://github.com/RemoteToHome-io/wg-udp-relay.git
cd wg-udp-relay
  1. Create your .env file from the example:
cp .env.example .env
  1. Edit .env with your configuration:
# Example .env configuration
LISTEN_PORTS=51820,443
ENDPOINT_DDNS=xxxxxxx.glddns.com
ENDPOINT_PORT=58120
  1. Start the relay:
docker compose up -d
  1. View logs:
docker compose logs -f
  1. Stop the relay:
docker compose down

Configuration

Environment Variables (.env file)

Variable Description Example Default
LISTEN_PORTS Comma-separated list of ports to listen on 51820,443 Required
ENDPOINT_DDNS Target WireGuard endpoint DDNS URL xxxxxxx.glddns.com Required
ENDPOINT_PORT Target WireGuard endpoint port 58120 Required
DNS_CHECK_INTERVAL How often to check for DNS changes 5m, 10m, 1h 5m

Important Note on DNS_CHECK_INTERVAL: For glddns.com DDNS servers, it is not advised to set the check interval lower than 5 minutes to avoid excessive DNS queries and potential rate limiting.

Docker Compose Configuration

The docker-compose.yml file uses network_mode: host to allow the container to:

  • Bind to any port on the host machine
  • Access the host's network interfaces directly
  • Support dynamic port configuration

WireGuard Client Configuration

To use the relay, you need to update your WireGuard client configuration to point to the relay server instead of connecting directly to your WireGuard endpoint.

Scenario

  • Original WireGuard Server: xxxxxxx.glddns.com:58120
  • Relay VPS: relay-vps.example.com (or VPS IP address)
  • Relay Listen Port: 443 (use HTTPS/3 QUIC UDP port to bypass restrictions)

Configuration Change

Before (Direct Connection):

[Interface]
PrivateKey = <your-private-key>
Address = 10.0.0.2/24
DNS = 1.1.1.1

[Peer]
PublicKey = <server-public-key>
Endpoint = xxxxxxx.glddns.com:58120
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

After (Through Relay):

[Interface]
PrivateKey = <your-private-key>
Address = 10.0.0.2/24
DNS = 1.1.1.1

[Peer]
PublicKey = <server-public-key>
Endpoint = relay-vps.example.com:443
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

What Changed?

  1. Endpoint hostname: Changed from xxxxxxx.glddns.com to relay-vps.example.com (your relay VPS)
  2. Endpoint port: Changed from 58120 to 443 (the port your relay is listening on)

Why This Works

  • The relay receives packets on port 443/UDP
  • The relay forwards packets to xxxxxxx.glddns.com:58120
  • Responses are sent back through the relay to your client
  • All WireGuard encryption remains end-to-end
  • To firewalls and ISPs, it appears as HTTPS/3 QUIC traffic on port 443/UDP

Benefits

  • Bypass port restrictions (many networks allow port 443/UDP for QUIC)
  • Bypass protocol restrictions
  • Maintain full WireGuard security
  • Automatic endpoint IP updates via DDNS monitoring

Important Notes

  • Keep the same PublicKey, PrivateKey, and Address values
  • Only change the Endpoint field
  • No changes needed on the WireGuard server itself
  • The relay is NOT transparent: The WireGuard server will see all connections from the relay's IP address, not individual client IPs
  • The relay performs SNAT (Source NAT) to ensure WireGuard handshakes complete successfully
  • End-to-end encryption is maintained - the relay cannot decrypt WireGuard traffic

MTU Considerations

You may need to adjust the MTU in your WireGuard client configuration for optimal performance on restricted networks:

[Interface]
PrivateKey = <your-private-key>
Address = 10.0.0.2/24
DNS = 1.1.1.1
MTU = 1384  # Lower MTU for networks with restrictions

Common MTU Values:

  • 1420 - Standard WireGuard MTU (default)
  • 1384 - Recommended for traveling/restricted networks
  • 1280 - Minimum for IPv6 compatibility

Important: The relay's UDP buffer size (default 1500 bytes) does not need to match your client's MTU setting. The relay buffer should remain at 1500 or higher because:

  • The relay receives already-encrypted WireGuard packets
  • It forwards packets without re-encapsulation or decryption
  • The buffer must be large enough to handle the complete packet including all headers
  • Client MTU controls packet size; relay buffer controls receive capacity

Manual Installation

From Source

Requirements:

  • Go 1.20 or later
git clone https://github.com/RemoteToHome-io/wg-udp-relay.git
cd wg-udp-relay
go build -o wg-udp-relay

Command-Line Usage

# Using command-line flags
./wg-udp-relay -ports 51820,51821 -target wg.example.com:51820

# Using environment variables
export LISTEN_PORTS=51820,51821
export TARGET_ENDPOINT=wg.example.com:51820
./wg-udp-relay

Command-Line Options

  • -ports <ports> - Comma-separated list of ports to listen on (or use LISTEN_PORTS env var)
  • -target <address> - Target WireGuard server address (or use TARGET_ENDPOINT env var)
  • -timeout <duration> - Connection idle timeout (default: 3m)
  • -buffer <size> - UDP buffer size in bytes (default: 1500, recommended to keep at 1500 or higher)
  • -dns-check <duration> - DNS resolution check interval (or use DNS_CHECK_INTERVAL env var, default: 5m)

Note on Buffer Size: The relay buffer size should remain at 1500 bytes or higher regardless of your WireGuard client MTU settings. The buffer must accommodate the complete encrypted packet as received from the network, while client MTU only controls the size of packets created by WireGuard. See MTU Considerations for details.

How It Works

The relay performs Source NAT (SNAT) on forwarded packets to ensure WireGuard compatibility:

  1. Relay listens on multiple configured UDP ports
  2. Client sends UDP packet to relay listen port
  3. Relay creates unique ephemeral port for this client session
  4. Relay forwards packet FROM ephemeral port TO WireGuard server
  5. Server sees traffic from relay IP (not client IP)
  6. Server responds to relay's ephemeral port
  7. Relay sends response back to client FROM listen port
  8. Sessions expire after the configured timeout period

Each listen port operates independently with its own session management.

Network Address Translation

The relay performs Source NAT (SNAT) on all forwarded packets:

Outbound (Client → Server):

  • Original: ClientIP:ClientPort → RelayIP:RelayListenPort
  • Forwarded: RelayIP:EphemeralPort → ServerIP:ServerPort

Inbound (Server → Client):

  • Received: ServerIP:ServerPort → RelayIP:EphemeralPort
  • Forwarded: RelayIP:RelayListenPort → ClientIP:ClientPort

Why SNAT is Required:

WireGuard performs "endpoint roaming" - it extracts the source IP address from UDP packets and uses it for direct responses. Without SNAT, the WireGuard server would see the client's real IP and attempt to respond directly, bypassing the relay entirely. SNAT ensures the server only sees the relay's IP address, maintaining the relay path for all traffic.

Important: This means the relay is NOT transparent - the WireGuard server will see all client traffic originating from the relay's IP address, not the original client IPs.

DDNS Monitoring

The relay automatically monitors the target endpoint's DNS record for IP changes:

  1. Initial Resolution: On startup, the DDNS hostname is resolved to an IP address
  2. Periodic Checks: Every DNS_CHECK_INTERVAL (default: 5 minutes), the relay re-resolves the hostname
  3. Change Detection: If the IP address has changed, the relay logs the change
  4. Session Migration: All active sessions are gracefully migrated to the new IP address
    • Old connections are closed
    • New connections are established to the new IP
    • Session state is preserved
    • No packet loss for active connections

This ensures the relay continues working even when your DDNS endpoint IP changes, which is common with dynamic DNS services.

Performance Optimization

For optimal throughput (200-300+ Mbps), system-level tuning is required. See the Performance Tuning Guide for detailed instructions.

Quick Summary:

  • Default Linux UDP buffers are too small for high-speed relay operations
  • Only the relay VPS needs tuning - client/server tuning is optional and provides no measurable benefit
  • Without tuning: 30-60 Mbps with 1-5% packet loss
  • With relay-only tuning: 200-300+ Mbps with zero packet loss
  • Real test results: 301.89 Mbps down / 164.73 Mbps up with relay-only tuning

10-minute setup:

# On relay VPS only
sudo tee /etc/sysctl.d/99-network-tuning.conf << EOF
# Increase socket buffer sizes for better network performance
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.core.rmem_default = 134217728
net.core.wmem_default = 134217728
net.core.netdev_max_backlog = 30000
EOF

sudo sysctl -p /etc/sysctl.d/99-network-tuning.conf
docker compose restart

This single configuration change typically provides a 5-10x throughput improvement. Client and server tuning are optional but testing shows no additional performance benefit.

Architecture

Clients              Relay Server                 WireGuard Server
-------              ------------                 ----------------
Client A  -->  Relay:51820 (listening)  -->  endpoint:51820
Client B  -->  Relay:443 (listening)    -->  endpoint:51820

Performance

  • Minimal CPU and memory footprint
  • Handles thousands of concurrent connections per port
  • Sub-millisecond forwarding latency
  • Optimized buffer management
  • Concurrent processing for multiple ports

Security Considerations

  • This relay does not decrypt or inspect WireGuard traffic
  • All WireGuard encryption remains end-to-end
  • The relay only forwards UDP packets between endpoints
  • Uses host networking for optimal performance and port access
  • Consider firewall rules to restrict relay access
  • Store .env file securely and never commit it to version control

Troubleshooting

Container won't start

  • Check that .env file exists and is properly configured
  • Verify ports are not already in use: sudo netstat -tulpn | grep <port>
  • Check logs: docker compose logs

Ports not accessible

  • Ensure network_mode: host is set in docker-compose.yml
  • Verify firewall rules allow UDP traffic on configured ports
  • Check SELinux/AppArmor policies if applicable

Connection issues

  • Verify ENDPOINT_DDNS resolves correctly: nslookup <domain>
  • Check that target endpoint is reachable: nc -zvu <endpoint> <port>
  • Review relay logs for error messages
  • Check for DNS change detection messages in logs

DNS not updating

  • Verify DNS_CHECK_INTERVAL is set appropriately in .env
  • Check logs for DNS resolution errors
  • Ensure the relay has network access to resolve DNS
  • Test manual resolution: dig +short <domain>

Professional Support

For professional assistance in deploying your own cloud relay for your self-hosted VPN setup, or using a managed relay through us, please contact RemoteToHome Consulting.

We offer expert consultation and deployment services for:

  • Self-hosted VPN solutions for remote work
  • Custom cloud based VPN servers and relay configurations
  • Custom configuration and setup reviews of GL.iNet routers for self-hosted VPNs
  • High-availability VPN setups
  • VPN performance optimization

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.

This ensures that any modifications or derivative works remain open source and benefit the community.

Acknowledgments

Built for use with WireGuard

"WireGuard" is a registered trademark of Jason A. Donenfeld.

About

Simple private high-speed UDP intermediate relay for WireGuard VPN tunnels that includes support for self-hosted VPNs using dynamic IP endpoints (e.g. personal VPN setups)

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors