{"record":{"id":"5db8893caabfc6e5","repo":"juanfont/headscale","slug":"initial-derpmap-is-empty-headscale-requires-at-le","errorCode":null,"errorMessage":"initial DERPMap is empty, Headscale requires at least one entry","messagePattern":"initial DERPMap is empty, Headscale requires at least one entry","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"hscontrol/app.go","lineNumber":57,"sourceCode":"\t\"github.com/pkg/profile\"\n\t\"github.com/rs/zerolog/log\"\n\t\"github.com/sasha-s/go-deadlock\"\n\t\"golang.org/x/crypto/acme\"\n\t\"golang.org/x/crypto/acme/autocert\"\n\t\"golang.org/x/sync/errgroup\"\n\t\"tailscale.com/envknob\"\n\t\"tailscale.com/tailcfg\"\n\t\"tailscale.com/types/dnstype\"\n\t\"tailscale.com/types/key\"\n\t\"tailscale.com/util/dnsname\"\n)\n\nvar (\n\terrSTUNAddressNotSet                   = errors.New(\"STUN address not set\")\n\terrUnsupportedLetsEncryptChallengeType = errors.New(\n\t\t\"unknown value for Lets Encrypt challenge type\",\n\t)\n\terrEmptyInitialDERPMap = errors.New(\n\t\t\"initial DERPMap is empty, Headscale requires at least one entry\",\n\t)\n)\n\nvar (\n\tdebugDeadlock        = envknob.Bool(\"HEADSCALE_DEBUG_DEADLOCK\")\n\tdebugDeadlockTimeout = envknob.RegisterDuration(\"HEADSCALE_DEBUG_DEADLOCK_TIMEOUT\")\n)\n\nfunc init() {\n\tdeadlock.Opts.Disable = !debugDeadlock\n\tif debugDeadlock {\n\t\tdeadlock.Opts.DeadlockTimeout = debugDeadlockTimeout()\n\t\tdeadlock.Opts.PrintAllCurrentGoroutines = true\n\t}\n}\n\nconst (","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/app.go#L39-L75","documentation":"Returned from Headscale.Serve() (hscontrol/app.go:568-570) when the DERP map assembled from the config has zero regions. Headscale requires at least one DERP region because Tailscale clients need a relay for NAT traversal; an empty map means the tailnet would be non-functional. The map is built by derp.GetDERPMap(h.cfg.DERP) and, if the embedded server is enabled with automatically_add_embedded_derp_region, its region is added — otherwise the map depends entirely on derp.urls entries.","triggerScenarios":"derp.paths/derp.urls empty or all fetches fail silently leaving zero regions, embedded DERP disabled (derp.server.enabled: false) and no derp.urls configured, or derp.urls pointing at a URL returning an empty/invalid DERPMap.","commonSituations":"Fully disabling the embedded DERP server while forgetting to list external derp.urls; a typo in the default https://controlplane.tailscale.com/derpmap/default URL; an air-gapped instance that cannot fetch the default DERP map and has no local file configured.","solutions":["Re-enable the embedded DERP server (derp.server.enabled: true with stun_addr set) so its region populates the map","Or configure derp.urls with at least one valid DERP map URL / local file path (e.g. a file:///etc/headscale/derpmap.yaml with one region)","Verify network egress to the DERP map URL if using the default Tailscale URL, or host the map locally"],"exampleFix":"# before\nderp:\n  server:\n    enabled: false\n  urls: []\n\n# after\nderp:\n  server:\n    enabled: false\n  urls:\n    - file:///etc/headscale/derpmap.yaml","handlingStrategy":"validation","validationCode":"# assert the effective DERP map will be non-empty before startup:\n# either embedded DERP is enabled, or at least one derp.urls entry exists\nserver_enabled=$(yq '.derp.server.enabled' config.yaml)\nurls_len=$(yq '.derp.urls | length' config.yaml)\n[ \"$server_enabled\" = \"true\" ] || [ \"$urls_len\" -gt 0 ] || { echo 'no DERP regions configured'; exit 1; }","typeGuard":null,"tryCatchPattern":"if err := h.Serve(); err != nil {\n    if errors.Is(err, errEmptyInitialDERPMap) {\n        log.Fatal().Msg(\"configure derp.urls or enable the embedded DERP server (with stun_addr)\")\n    }\n    return err\n}","preventionTips":["If disabling the embedded DERP server, always add derp.urls in the same change","Monitor control_logs for 'getting DERPMap' errors when derp.urls fetches fail","Air-gapped deployments: serve the DERP map from a local file:// URL"],"tags":["config","derp","relay","startup","headscale"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}