DERP (Designated Encrypted Relay for Packets) is the relay and connectivity component of Tailscale. It primarily handles two tasks:

  1. Assisting nodes with NAT traversal (hole-punching) to establish direct connections
  2. Relaying traffic when direct connections fail

Two deployment approaches:

  • Docker: Quick to set up, relies on the container ecosystem, upgrade by swapping the image
  • Non-Docker: System service + binary, closer to system-level operations, upgrade by replacing the binary

Quick Navigation

After deployment: §6 ACL Configuration§7 Client Reconnection§8 Testing & Verification


1. Deployment Parameters

This guide uses placeholders for values that need to be replaced. When you encounter these placeholders in subsequent steps, replace them with your actual values.

Placeholder Description Example
<VPS_IP> VPS public IP xxx.xxx.xxx.xxx
<DERP_TCP_PORT> DERP TCP port 13477
<STUN_UDP_PORT> STUN UDP port 13478
<REGION_ID> Custom DERP region ID 900
<REGION_CODE> Custom DERP region code myderp
<RELAY_HOSTNAME> DERP server’s Tailscale IP (first column of tailscale status) 100.x.x.x

💡 Tip: If you want to do a quick run-through first, you can replace <VPS_IP>, <DERP_TCP_PORT>, <STUN_UDP_PORT> with xxx.xxx.xxx.xxx, 13477, 13478.


2. Prerequisites

Open the following ports in your cloud security group and system firewall:

  • <DERP_TCP_PORT>/tcp: DERP service port
  • <STUN_UDP_PORT>/udp: STUN service port

⚠️ Warning: Check both cloud security group and system firewall rules — local access may work while external access fails if either layer is misconfigured.

2.1 Optional: Switch Package Mirror (China)

When package downloads are slow on networks in mainland China, switch to a mirror by OS:

Debian / Ubuntu

# Back up original sources
cp /etc/apt/sources.list /etc/apt/sources.list.bak

# Switch to Aliyun mirror (Ubuntu 22.04 example; replace jammy for other versions)
sed -i 's|http://archive.ubuntu.com|https://mirrors.aliyun.com|g' /etc/apt/sources.list

apt update

RHEL / CentOS / Rocky

# Rocky Linux 9 example
sed -i 's|https://dl.rockylinux.org|https://mirrors.aliyun.com/rockylinux|g' /etc/yum.repos.d/rocky*.repo

dnf makecache

Alpine {#apk-mirror}

cp /etc/apk/repositories /etc/apk/repositories.bak

# Version must match your Alpine — check with: cat /etc/alpine-release
cat > /etc/apk/repositories << 'EOF'
https://mirrors.aliyun.com/alpine/v3.22/main
https://mirrors.aliyun.com/alpine/v3.22/community
EOF

apk update

3. Docker Deployment

3.1 Install Docker and Compose

OS Install Command
Debian / Ubuntu apt update && apt install -y docker.io docker-compose-plugin
RHEL / CentOS / Rocky dnf install -y docker docker-compose-plugin
Alpine apk add docker docker-compose

ℹ️ Note: On Alpine, Compose is typically V1 (docker-compose). For V2, you can install the docker-compose-plugin binary separately.

3.2 Enable Docker Service

OS Command
Debian / Ubuntu / RHEL systemctl enable --now docker
Alpine rc-update add docker boot && service docker start

Verify installation:

docker --version
# Debian/Ubuntu/RHEL use:
docker compose version
# Alpine uses:
docker-compose --version

3.3 Docker Registry Mirror (Optional)

When pulling Docker images is slow (primarily for users in mainland China), configure registry mirrors. This persists after writing:

mkdir -p /etc/docker

cat > /etc/docker/daemon.json << 'EOF'
{
  "registry-mirrors": [
    "https://docker.1ms.run",
    "https://dockerproxy.com",
    "https://docker.m.daocloud.io"
  ]
}
EOF

Restart Docker: run systemctl restart docker on Debian / Ubuntu / RHEL, or service docker restart on Alpine.

💡 Tip: For low-spec VPS, you can add the following parameters to daemon.json:

{
  "registry-mirrors": ["..."],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "max-concurrent-downloads": 2
}
  • log-opts: Limits each log file to 10MB with 3 files retained, preventing disk from filling up
  • max-concurrent-downloads: Limits concurrent layer downloads to 2, reducing memory and bandwidth usage

3.4 Create docker-compose.yml

All systems use the same file. Replace placeholders with actual values (see §1):

services:
  derper:
    image: ghcr.io/yangchuansheng/ip_derper:latest  # No official release tags yet; pull latest and re-pull to upgrade
    container_name: derper
    restart: unless-stopped
    ports:
      - <DERP_TCP_PORT>:<DERP_TCP_PORT>  # Replace with DERP port, e.g., 13477
      - <STUN_UDP_PORT>:3478/udp         # Replace with STUN port, e.g., 13478
    environment:
      - DERP_ADDR=:<DERP_TCP_PORT>       # Replace with DERP port, format: :<port>
      - DERP_VERIFY_CLIENTS=true          # Enable client verification to prevent public abuse
    volumes:
      - /var/run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sock

⚠️ Warning: The STUN internal container port is fixed at 3478/udp; the left side is the host-mapped port. Make sure to open the corresponding host UDP port in your firewall.

3.5 Start and Verify

OS Start Command Port Check Command
Debian / Ubuntu / RHEL docker compose up -d ss -tulnp \| grep -E "<DERP_TCP_PORT>\|<STUN_UDP_PORT>"
Alpine docker-compose up -d netstat -tulnp \| grep -E "<DERP_TCP_PORT>\|<STUN_UDP_PORT>"

Common verification:

docker logs derper

4. Non-Docker Deployment

ℹ️ Note: The official Tailscale package does not include the derper binary; it needs to be compiled from source.

4.1 System Preparation

OS Command
Debian / Ubuntu apt update && apt upgrade -y && apt install -y wget openssl curl netcat-openbsd
RHEL / CentOS / Rocky dnf update -y && dnf install -y wget openssl curl nmap-ncat
Alpine apk update && apk upgrade && apk add wget openssl curl netcat-openbsd

💡 Tip: When package downloads are slow on networks in mainland China, refer to §2.1 Switch Package Mirror.

4.2 Create Directory Structure

All systems use the same command:

mkdir -p /usr/local/derp/{bin,certs}

4.3 Install Go Environment

OS Install Command
Debian / Ubuntu apt install -y golang
RHEL / CentOS / Rocky dnf install -y golang
Alpine apk add go

Verify: go version

If you are in mainland China, switch the Go module proxy (Go 1.13+, all systems):

go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct

4.4 Compile derper

Compilation requires >= 2GB RAM:

go install tailscale.com/cmd/derper@latest
cp ~/go/bin/derper /usr/local/derp/bin/

💡 Tip: Low-memory VPS may encounter OOM during compilation. Use §4.9 Cross-Compilation on Another Machine instead, then return to “§4.5 Verify Compilation Result” to continue.

4.5 Verify Compilation Result

chmod +x /usr/local/derp/bin/derper

# Verify it's a static binary
ldd /usr/local/derp/bin/derper  # Should output "not a dynamic executable"

# Verify version
/usr/local/derp/bin/derper --version

ℹ️ Note: --version output like 1.xx.x-ERR-BuildInfo is normal (go install builds don’t contain complete build info).

If you encounter Permission denied, /usr/local may be mounted with noexec:

# Check mount options
mount | grep /usr/local

# If noexec is present, copy to another location to run
cp /usr/local/derp/bin/derper /root/derper
chmod +x /root/derper
/root/derper --version

After confirming it works, move the binary back to /usr/local/derp/bin/ or adjust the mount options.

4.6 Generate Self-Signed Certificate

⚠️ Warning: Certificate filenames must match the -hostname parameter. It’s recommended to use <VPS_IP>.crt and <VPS_IP>.key directly.

cd /usr/local/derp/certs

# Replace all <VPS_IP> below with your server's public IP
openssl req -x509 -newkey rsa:2048 -sha256 -days 3650 \
  -nodes -keyout <VPS_IP>.key -out <VPS_IP>.crt \
  -subj "/CN=<VPS_IP>" \
  -addext "subjectAltName=IP:<VPS_IP>"

Parameter explanations:

  • -keyout <VPS_IP>.key: Private key output file, filename must match -hostname
  • -out <VPS_IP>.crt: Certificate output file, filename must match -hostname
  • -subj "/CN=<VPS_IP>": Certificate Common Name, set to server IP
  • -addext "subjectAltName=IP:<VPS_IP>": SAN extension, must include the IP
  • -days 3650: Valid for 10 years

4.7 Create Service and Start

4.7.1 systemd (Debian / Ubuntu / RHEL)

cat > /etc/systemd/system/derper.service << 'EOF'
[Unit]
Description=Tailscale DERP Server
After=network.target
Wants=network.target

[Service]
User=root
Group=root
# Replace <VPS_IP>, <DERP_TCP_PORT>, <STUN_UDP_PORT> below with actual values (see §1)
ExecStart=/usr/local/derp/bin/derper \
  -certmode=manual \
  -certdir=/usr/local/derp/certs \
  -hostname=<VPS_IP> \
  -a :<DERP_TCP_PORT> \
  -stun-port=<STUN_UDP_PORT> \
  -http-port=-1 \
  --verify-clients
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

Key parameters:

  • -certmode=manual: Manual certificate mode
  • -http-port=-1: Disable HTTP port
  • --verify-clients: Enable client verification
  • Restart=on-failure + RestartSec=5: Auto-restart on crash

Start and verify:

systemctl daemon-reload
systemctl enable derper
systemctl start derper
systemctl status derper

4.7.2 OpenRC (Alpine)

cat > /etc/init.d/derper << 'EOF'
#!/sbin/openrc-run

description="Tailscale DERP Server"

command="/usr/local/derp/bin/derper"
# Replace <VPS_IP>, <DERP_TCP_PORT>, <STUN_UDP_PORT> below with actual values (see §1)
command_args="-certmode=manual -certdir=/usr/local/derp/certs -hostname=<VPS_IP> -a :<DERP_TCP_PORT> -stun-port=<STUN_UDP_PORT> -http-port=-1 --verify-clients"
command_user="root:root"
supervisor=supervise-daemon
respawn_delay=5
respawn_max=0

depend() {
    need net
    after firewall
}
EOF

Key parameters:

  • -certmode=manual: Manual certificate mode
  • -http-port=-1: Disable HTTP port
  • --verify-clients: Enable client verification
  • supervisor=supervise-daemon + respawn_delay=5: Auto-restart on crash (equivalent to systemd’s Restart=on-failure)

Start and verify:

chmod +x /etc/init.d/derper
rc-update add derper default
rc-service derper start
rc-service derper status

4.8 Verify Port Listening

OS Port Check Command
Debian / Ubuntu / RHEL ss -tulnp \| grep -E "<DERP_TCP_PORT>\|<STUN_UDP_PORT>"
Alpine netstat -tulnp \| grep -E "<DERP_TCP_PORT>\|<STUN_UDP_PORT>"

Common verification:

# Local TCP connectivity (replace with DERP port)
nc -zv 127.0.0.1 <DERP_TCP_PORT>

# View process
ps aux | grep derper

Expected output:

tcp        0      0 :::<DERP_TCP_PORT>      :::*        LISTEN      1234/derper
udp        0      0 :::<STUN_UDP_PORT>      :::*                    1234/derper

If a line is missing, the corresponding port is not listening. Check the service logs.

View logs:

OS Command
Debian / Ubuntu / RHEL journalctl -u derper --no-pager -n 30
Alpine dmesg \| tail -20

4.9 Cross-Compilation on Another Machine (Low-Memory VPS)

VPS with less than 2GB RAM will OOM during compilation. You can compile on another machine and upload the binary.

# 1) Prepare Go environment (version >= 1.21 recommended)
go version

# 2) If in mainland China, switch the Go module proxy
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct

# 3) Set target platform (example: Linux AMD64)
export GOOS=linux
export GOARCH=amd64    # For ARM64, change to arm64
export CGO_ENABLED=0

# 4) Build derper
go install tailscale.com/cmd/derper@latest
ls ~/go/bin/derper

# 5) Create directory on VPS (replace <VPS_IP> with server public IP)
ssh root@<VPS_IP> "mkdir -p /usr/local/derp/bin /usr/local/derp/certs"

# 6) Upload to VPS
scp ~/go/bin/derper root@<VPS_IP>:/usr/local/derp/bin/

# 7) Verify on VPS
ssh root@<VPS_IP>
chmod +x /usr/local/derp/bin/derper
ldd /usr/local/derp/bin/derper    # Should output "not a dynamic executable"
/usr/local/derp/bin/derper --version

Compilation parameter notes:

  • CGO_ENABLED=0: Generates a pure static binary, reducing runtime environment dependencies
  • GOOS / GOARCH: Target operating system and architecture

💡 Tip: After uploading and verifying, return to §4.6 “Generate Self-Signed Certificate” to complete certificates, service configuration, and Tailscale installation.


5. Install Tailscale Client

Debian / Ubuntu / RHEL use the official one-line script:

curl -fsSL https://tailscale.com/install.sh | sh
systemctl enable --now tailscaled
tailscale up

Alpine installs via apk (the official one-line script is not supported):

Ensure /etc/apk/repositories contains the community repository (using Alpine 3.19 as an example):

http://dl-cdn.alpinelinux.org/alpine/v3.19/main
http://dl-cdn.alpinelinux.org/alpine/v3.19/community

If missing, append it:

echo "http://dl-cdn.alpinelinux.org/alpine/v3.19/community" >> /etc/apk/repositories

Install and start:

apk update
apk add tailscale
rc-update add tailscale default
rc-service tailscale start
tailscale up

Complete the authentication link shown in the terminal, then run tailscale status to confirm.


6. Configure ACL (derpMap)

Go to the Tailscale admin console → Access controls → JSON editor, and merge the following derpMap into your existing ACL (do not overwrite other configuration items).

Replace <REGION_ID>, <REGION_CODE>, <VPS_IP>, <DERP_TCP_PORT>, <STUN_UDP_PORT> in the JSON below with your actual values (see §1).

{
  "derpMap": {
    "OmitDefaultRegions": false,        // Keep official DERP as fallback
    "Regions": {
      "<REGION_ID>": {
        "RegionID": <REGION_ID>,         // Custom region ID, recommend 900+
        "RegionCode": "<REGION_CODE>",   // Custom region code
        "RegionName": "Custom DERP",
        "Nodes": [
          {
            "Name": "myderper",
            "RegionID": <REGION_ID>,
            "HostName": "<VPS_IP>",      // Must match certificate and -hostname
            "DERPPort": <DERP_TCP_PORT>, // Must match service listening port
            "STUNPort": <STUN_UDP_PORT>, // Must match STUN listening port
            "CanPort80": false,
            "InsecureForTests": true     // Required for IP + self-signed certificate scenarios
          }
        ]
      }
    }
  }
}

ℹ️ Note: The Tailscale ACL editor supports HuJSON format, so // comments can be kept as-is without removal.

⚠️ Warning: Common pitfalls:

  • Trailing commas in JSON causing save failures
  • ACL ports not matching service listening ports
  • HostName not matching certificate SAN

7. Client Reconnection and Configuration Propagation

After saving the ACL, it is recommended to reconnect clients to ensure the new configuration is pulled:

OS Command
Debian / Ubuntu / RHEL systemctl restart tailscaled
Alpine rc-service tailscale restart
Universal tailscale down && tailscale up

Wait 1-2 minutes before running connectivity tests.

7.1 Enable Peer Relay (Optional)

ℹ️ Note: Peer Relay is currently in Beta and requires Tailscale 1.86+. It allows devices in your tailnet to act as high-throughput relay servers. When direct connections aren’t possible, Tailscale tries available Peer Relays first before falling back to DERP servers. Useful for large file transfers, HD streaming, and other high-throughput scenarios behind strict NATs.

Step 1: Enable Peer Relay on the relay device

# Verify version >= 1.86
tailscale version

# Enable Peer Relay on a specified UDP port
tailscale set --relay-server-port=40000

⚠️ Warning: Ensure the UDP port (e.g., 40000/udp) is open in both your firewall and cloud security group, otherwise other devices cannot connect through the Peer Relay.

Step 2: Add a grant policy in ACL

Enabling the port alone is not enough. You must add a Peer Relay grants policy in the Tailscale admin console under Access controls.

First, find your DERP server’s Tailscale IP:

tailscale status

The first column 100.x.x.x is the Tailscale IP.

Append the Peer Relay entry to the grants array in your existing ACL (multiple grants can coexist). Below is a complete working example with basic network access and Peer Relay. Replace <RELAY_HOSTNAME> with the DERP server’s Tailscale IP:

{
  "grants": [
    {
      "src": ["*"],
      "dst": ["*"],
      "ip": ["*"]                        // Basic network access, allows all devices to communicate
    },
    {
      "src": ["*"],
      "dst": ["<RELAY_HOSTNAME>"],       // Replace with your DERP server's Tailscale IP
      "app": {
        "tailscale.com/cap/relay": []    // Relay capability, no additional parameters needed
      }
    }
  ],

  "acls": [
    {
      "action": "accept",
      "src": ["*"],
      "dst": ["*:*"]                     // Allow all traffic
    }
  ]
}

⚠️ Warning: Tailscale ACL defaults to denying all traffic. Without acls rules or a network access grant (the first "ip": ["*"] entry), devices cannot communicate and the Peer Relay won’t work either. The example above includes both to ensure proper operation.

If you already have your own acls and grants configuration, simply append the second Peer Relay grant to the end of your existing grants array.

  • dst: Must replace <RELAY_HOSTNAME> with the DERP server’s Tailscale IP
  • tailscale.com/cap/relay: Required relay capability declaration

💡 Tip: For multiple Peer Relays or finer control, you can use tags instead. See Tailscale Peer Relays documentation for details.

Step 3: Verify

# Generate traffic and check status
tailscale status | grep peer-relay

# Or test with ping
tailscale ping <peer-device>

When a connection uses a Peer Relay, tailscale status shows peer-relay instead of direct or relay.

Disable Peer Relay

tailscale set --relay-server-port=""

7.2 Enable Exit Node (Optional)

Linux requires IP forwarding to be enabled before it can act as an Exit Node:

# If your system has /etc/sysctl.d/ directory (recommended)
echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
sudo sysctl -p /etc/sysctl.d/99-tailscale.conf

# Otherwise use /etc/sysctl.conf
echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf
echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p /etc/sysctl.conf

⚠️ Warning: If using firewalld, you also need to allow masquerading:

firewall-cmd --permanent --add-masquerade

After enabling IP forwarding, ensure your firewall denies forwarded traffic by default (ufw and firewalld do this by default) to prevent routing unintended traffic.

Advertise the Exit Node and enable route acceptance:

sudo tailscale up --accept-routes --advertise-exit-node

Finally, approve the Exit Node from the Tailscale admin console:

  1. Open the Machines page and locate the device
  2. Click the ... menu on the device → Edit route settings
  3. Enable Use as exit node

💡 Tip: If the device is authenticated by a user with Exit Node permissions configured in ACL autoApprovers, it will be approved automatically.

7.3 Network Performance Optimization (Optional)

Enabling UDP GRO forwarding and disabling GRO list can improve Tailscale network throughput:

# Enable UDP GRO forwarding
ethtool -K eth0 rx-udp-gro-forwarding on

# Enable UDP segmentation offload
ethtool -K eth0 rx-gro-list off

# Verify settings
ethtool -k eth0 | grep gro

💡 Tip: If you get ethtool: command not found:

OS Install Command
Alpine apk add ethtool
Debian / Ubuntu apt install -y ethtool
RHEL / CentOS / Rocky dnf install -y ethtool

8. Testing and Verification

8.1 Quick Test Commands

# Network capability check (UDP / DERP)
tailscale netcheck

# Device and peer node status
tailscale status

# Structured status (useful for debugging and documentation)
tailscale status --json

8.2 tailscale netcheck Output

  • UDP: true: UDP communication is working
  • Nearest DERP: Should be close to your custom region
  • DERP latency: Lower latency on your self-hosted node is better

8.3 tailscale status Output

  • Whether your machine shows active
  • Whether peer nodes are visible
  • Whether the connection path shows relay "<REGION_CODE>" or direct ...

8.4 Example Output

ℹ️ Note: Output may vary slightly between versions; focus on the interpretation approach.

Example 1: tailscale netcheck

Report:
        * UDP: true
        * IPv4: yes, 203.0.113.24:41641
        * IPv6: no, but OS has support
        * MappingVariesByDestIP: false
        * HairPinning: false
        * Nearest DERP: myderp
        * DERP latency:
                - myderp: 18.2ms
                - tok: 62.4ms
  • UDP: true: STUN hole-punching capability is working
  • Nearest DERP: myderp: Client has connected to the custom region
  • MappingVariesByDestIP: false: Usually indicates more stable NAT mapping

Example 2: tailscale status

100.64.0.2   vps-derp   linux    active; direct 198.51.100.10:41641, tx 12 rx 20
100.64.0.8   laptop     windows  active; relay "myderp", tx 3 rx 6
  • active; direct ...: Currently using direct connection
  • active; relay "myderp": Currently relaying through DERP, region is myderp
  • If all connections remain relay long-term, investigate NAT/firewall/port policies