Self-Hosted Tailscale DERP Relay Server
When using Tailscale's official DERP relay servers in China, you often encounter high latency and unstable connections. Self-hosting a DERP server on a VPS with a public IP can significantly improve communication latency between nodes and hole-punching success rate. Below are two Docker-based deployment methods: domain reverse proxy mode and pure IP mode.
Prerequisites
- A VPS with a public IP (recommended to choose a region with low latency)
- Docker and Docker Compose installed
- Domain with DNS configured (required for Method 1, not for Method 2)
- Nginx (or other reverse proxy) to handle SSL (required for Method 1, not for Method 2)
Method 1: Domain Deployment for DERP (Reverse Proxy Mode)
Docker Compose Configuration
Create docker-compose.yml:
Key Configuration Explanation
| Configuration Item | Description |
|---|---|
| network_mode: host | Required. Uses the host's network stack, supports IPv6 and STUN |
| DERP_DOMAIN | Your DERP server domain |
| DERP_ADDR | Listening address and port |
| DERPVERIFYCLIENTS | Set to true to verify client identity and prevent abuse |
| tailscaled.sock | Mount Tailscale socket for client verification |
⚠️ Note:
DERP_VERIFY_CLIENTS=truerequires running the Tailscale client on the server and mounting its socket file.
Start Service
Nginx Reverse Proxy Configuration
DERP uses WebSocket for communication, so the reverse proxy must be configured correctly.
Important Configuration Explanations
- Force WebSocket headers:
proxy_set_header Upgrade "websocket"andConnection "upgrade"are required; otherwise, a 426 error will occur under HTTP/2 - Use HTTP/1.1:
proxy_http_version 1.1ensures WebSocket works properly - Disable buffering:
proxy_buffering offprevents relay data from being cached - Long timeout: 86400 seconds (24 hours) to keep connections from disconnecting
Reload Nginx:
Tailscale ACL Configuration
Log in to the Tailscale Admin Console, go to Access Controls, and add the derpMap configuration:
Configuration Explanation
| Configuration Item | Description |
|---|---|
| OmitDefaultRegions | Set to true to disable official DERP and use only self-hosted |
| RegionID | Custom region ID, recommended to use 900+ to avoid conflicts |
| RegionCode | Region code, e.g., hkg, sgp, tyo |
| HostName | DERP server domain |
| DERPPort | DERP port, 443 after Nginx reverse proxy |
| STUNPort | STUN port, keep as 3478 |
Optional: Specify IP Address
If you don't want to use DNS resolution, you can specify the IP directly:
Method 2: Domainless DERP Deployment (Pure IP Mode)
If you don't have a domain or don't want to deal with DNS and SSL certificates, you can deploy the DERP server directly using an IP address. This method uses the ip_derper image, which automatically generates a self-signed certificate without needing Nginx reverse proxy.
Docker Compose Configuration
Create docker-compose.yml:
Key Configuration Explanation
| Configuration Item | Description |
|---|---|
| ip_derper image | Modified from official derper, supports pure IP operation, automatically generates self-signed certificate |
| DERP_ADDR | Listening port, using 13477 here |
| DERP_CERTS | Self-signed certificate storage path (automatically generated inside container) |
| DERPVERIFYCLIENTS | Set to true to verify client identity and prevent abuse |
| network_mode: host | Uses the host's network stack, supports IPv6 and STUN |
💡 Difference from the domain version: The domainless version does not require Nginx reverse proxy configuration; the DERP server directly exposes ports and uses self-signed TLS certificates.
Start Service
ACL Configuration
Log in to the Tailscale Admin Console, go to Access Controls, and add the derpMap configuration:
Core Configuration Explanation
| Configuration Item | Description |
|---|---|
| OmitDefaultRegions | false means use both self-hosted and official DERP; true means only self-hosted |
| IPv4 / IPv6 | Directly specify server IP address, no DNS resolution |
| InsecureForTests | Core configuration: set to true to allow skipping TLS certificate validation (due to self-signed certificate) |
| DERPPort | Matches the port in DERP_ADDR configuration, 13477 here |
| STUNPort | STUN port, default 3478; set to -1 to disable STUN for this node |
⚠️ Note: Although
InsecureForTestsincludes "ForTests" in the name, it is an official Tailscale configuration item to skip TLS validation, required in pure IP domainless scenarios.
Server Firewall Configuration
Regardless of the deployment method chosen, you must open the corresponding ports in the system firewall and cloud provider security groups (e.g., Alibaba Cloud, Tencent Cloud):
| Method | Port | Protocol | Purpose |
|---|---|---|---|
| Method 1 (reverse proxy) | 443 | TCP | Nginx receives HTTPS/DERP traffic |
| Method 2 (pure IP) | 13477 | TCP | Native DERP Docker relay listening |
| All methods | 3478 | UDP | STUN protocol for NAT hole punching |
Verifying DERP Server
Check DERP Status
Run on any Tailscale client:
The output should display your custom DERP region and its latency:
Check Connection Method
If it shows relay "hkg" or similar, it means your DERP relay is being used.
Common Issues
Q: Status shows IPv6: No
Check the following:
- Does the server have an IPv6 address
- Is DNS configured with AAAA records
- Is Docker using
network_mode: host - Is the firewall allowing IPv6
Q: 426 Upgrade Required Error
Nginx configuration issue, ensure:
proxy_set_header Upgrade "websocket"is setproxy_set_header Connection "upgrade"is setproxy_http_version 1.1is used
Q: Client Verification Failed
Ensure:
- Tailscale client is running on the server
/var/run/tailscale/tailscaled.sockis correctly mounted- The container has permission to access the socket
Summary
The main purposes of self-hosting a Tailscale DERP server:
- Access private nodes, reduce remote communication latency.
- Traffic does not go through official servers.
- Optionally deploy nodes in specific regions like domestic cloud servers.
- Supports IPv6, improving P2P hole-punching success probability.
Key configuration points:
- Docker's
network_modemust be set tohost. - In domain reverse proxy mode, Nginx must include WebSocket headers (
Upgrade "websocket"etc.) to avoid 426 errors. - In pure IP mode, ACL configuration must include
"InsecureForTests": true. OmitDefaultRegionsin ACL controls whether to disable official DERP nodes.