# Headscale Derp

> Configure and manage DERP relay servers for Tailscale/Headscale — embedded and standalone DERP deployment, latency testing, and connectivity diagnostics. Use when direct peer connections fail, traffic is routed through DERP relays, or setting up custom relay regions.

- Skill: `magnus919/headscale-derp` (Agent Skill, multi-file: 6 files)
- Install (CLI): `npx skillmds@latest add magnus919/headscale-derp`
- Raw SKILL.md: https://api.skillmd.com/api/skills/magnus919/headscale-derp/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: magnus919 (https://skillmd.com/u/magnus919)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/magnus919/headscale-derp

---


# headscale-derp Skill

## Overview

**DERP** = **D**esignated **E**ncrypted **R**elay **P**rotocol. DERP is Tailscale's TURN-like fallback mechanism used when direct peer-to-peer NAT traversal fails. Traffic through DERP is fully encrypted (WireGuard inside TLS), but it routes through a relay server rather than directly between peers, so it introduces additional latency.

Common scenarios where DERP is used:

- **Symmetric NAT** — Both peers behind symmetric NAT gateways
- **Corporate firewalls** — Restrictive egress policies that block UDP/STUN
- **Double NAT** — Carrier-grade NAT on both sides
- **Blocked STUN** — UDP port 3478 is filtered

## DERP in Headscale

Headscale includes an **embedded DERP server** that is enabled by default in the `config.yaml`:

```yaml
derp:
  server:
    enabled: true
    region_id: 999
    region_code: "headscale"
    region_name: "Headscale Embedded DERP"
    stun_listen_addr: "0.0.0.0:3478"
    private_key_path: "/var/lib/headscale/derp_server_private.key"
```

The embedded server runs a STUN endpoint on port 3478 and relays on port 443 (or whatever port Headscale listens on). It's suitable for small tailnets (under ~50 nodes).

### Key config options

| Option | Default | Description |
|--------|---------|-------------|
| `derp.server.enabled` | `true` | Enable the embedded DERP server |
| `derp.server.region_id` | `999` | Numeric region identifier (must be unique across all regions) |
| `derp.urls` | `[]` | Additional DERP map URLs (for standalone servers) |
| `derp.paths` | `[]` | Local DERP map JSON file paths |
| `derp.auto_update` | `true` | Automatically fetch Tailscale's default DERP map |
| `derp.stun_listen_addr` | `0.0.0.0:3478` | STUN listener address |

## DERP Map

A DERP map is a JSON structure that defines relay regions and nodes. Example:

```json
{
  "Regions": {
    "900": {
      "RegionID": 900,
      "RegionCode": "us-nyc",
      "RegionName": "New York",
      "Nodes": [
        {
          "Name": "900a",
          "RegionID": 900,
          "HostName": "derp-nyc.example.com",
          "DERPPort": 443,
          "STUNPort": 3478,
          "STUNOnly": false
        }
      ]
    }
  }
}
```

The DERP map can be served via HTTPS URL (put in `derp.urls`) or as a local JSON file (put in `derp.paths`).

## Connectivity Testing

Run `tailscale netcheck` to see which DERP regions are reachable and their latency:

```
$ tailscale netcheck
Report:
    * UDP: true
    * IPv4: yes
    * IPv6: no
    * MappingVariesByDestIP: true
    * HairPinning: false
    * PortMapping: UPnP
    * Nearest DERP: Dallas
    * DERP latency:
        - dallas: 18ms (dallas)
        - us-west: 35ms (us-west)
        - new-york-city: 5ms (new-york-city)
        - london: 85ms (london)
```

## Standalone DERP

For larger tailnets or dedicated relay capacity, run a standalone DERP server using the official `tailscale/derper` Docker image:

```bash
docker run -d \
  --name=derper \
  --restart=always \
  -p 3478:3478/udp \
  -p 443:443 \
  -v /etc/letsencrypt:/certs \
  -v /var/lib/derper:/var/lib/derper \
  tailscale/derper \
  --hostname=derp.example.com
```

## TLS Certificates

DERP requires TLS. Recommended approaches:

1. **Let's Encrypt (auto)** — `tailscale/derper` supports automatic certificate issuance via Let's Encrypt. It listens on port 80 for the ACME HTTP-01 challenge.
2. **Manual certs** — Pass `--cert=/path/to/cert.pem --key=/path/to/key.pem` to the `derper` binary.
3. **Reverse proxy** — Terminate TLS at a reverse proxy (nginx, Caddy, Traefik) and forward to the local DERP port.

## Region Selection

Tailscale clients automatically select the DERP region with the lowest latency. The selection algorithm:

1. Sends STUN requests to all configured DERP regions
2. Measures round-trip time for each
3. Picks the region with the lowest latency
4. Falls back to the next-closest region if connectivity fails

## Gotchas

- **DERP is encrypted but slower** — All DERP traffic is WireGuard-inside-TLS, adding ~5-15ms overhead. Direct connections are always preferred.
- **Embedded DERP works for small tailnets** — The embedded server in Headscale uses the same Headscale process for relaying. Under heavy relay traffic, it can impact Headscale control-plane performance. For >50 nodes doing active relay, deploy a standalone DERP.
- **All traffic routes through DERP if STUN is blocked** — If UDP port 3478 is blocked anywhere in the network path, Tailscale cannot establish direct peer-to-peer connections and will route ALL traffic through the nearest DERP relay.
- **Port 3478 (STUN) and 443 (DERP) must be open** — STUN uses UDP for NAT traversal probing; DERP uses TCP/TLS for relay traffic. Both must be reachable from clients.
- **Multiple regions improve resilience** — Deploy DERP relays in at least two geographically diverse locations so clients have a fallback.
- **DERP map caching** — Clients cache the DERP map. If you add a new region, it can take up to 5 minutes for clients to pick it up. Use `tailscale netcheck` to force a refresh.

## Trigger Conditions

This skill is activated by keywords: `DERP`, `relay`, `peer relay`, `STUN`, `direct connection failed`

## When not to use

Do not use this skill when direct peer connections work — DERP tuning is only needed when NAT traversal fails. For general client connectivity diagnostics, load `tailscale-client` instead.

