{"record":{"id":"35b93c812021b59b","repo":"juanfont/headscale","slug":"binding-to-tcp-address-w","errorCode":null,"errorMessage":"binding to TCP address: %w","messagePattern":"binding to TCP address: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"hscontrol/app.go","lineNumber":711,"sourceCode":"\t\tHandler:     router,\n\t\tReadTimeout: types.HTTPTimeout,\n\n\t\t// Long polling should not have any timeout, this is overridden\n\t\t// further down the chain\n\t\tWriteTimeout: types.HTTPTimeout,\n\t}\n\n\tvar httpListener net.Listener\n\n\tif tlsConfig != nil {\n\t\thttpServer.TLSConfig = tlsConfig\n\t\thttpListener, err = tls.Listen(\"tcp\", h.cfg.Addr, tlsConfig)\n\t} else {\n\t\thttpListener, err = new(net.ListenConfig).Listen(context.Background(), \"tcp\", h.cfg.Addr)\n\t}\n\n\tif err != nil {\n\t\treturn fmt.Errorf(\"binding to TCP address: %w\", err)\n\t}\n\n\terrorGroup.Go(func() error { return httpServer.Serve(httpListener) })\n\n\tlog.Info().\n\t\tMsgf(\"listening and serving HTTP on: %s\", h.cfg.Addr)\n\n\t// Only start debug/metrics server if address is configured\n\tvar debugHTTPServer *http.Server\n\n\tvar debugHTTPListener net.Listener\n\n\tif h.cfg.MetricsAddr != \"\" {\n\t\tdebugHTTPListener, err = (&net.ListenConfig{}).Listen(ctx, \"tcp\", h.cfg.MetricsAddr)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"binding to TCP address: %w\", err)\n\t\t}\n","sourceCodeStart":693,"sourceCodeEnd":729,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/app.go#L693-L729","documentation":"Returned when the main HTTP listener cannot bind h.cfg.Addr (hscontrol/app.go:703-711). With TLS configured it calls tls.Listen(\"tcp\", addr, tlsConfig); otherwise net.ListenConfig.Listen(\"tcp\", addr). This is the primary control-plane address (listen_addr); a failure here is fatal to startup and the wrapped error is the OS bind error.","triggerScenarios":"Another process already holds the port (EADDRINUSE) — a second headscale, tailscaled, or a reverse proxy; binding a port below 1024 as non-root (EACCES); listen_addr specifies an IPv6 address on a host with IPv6 disabled (EADDRNOTAVAIL); malformed listen_addr such as a bare port without a colon; firewall/SELinux blocking the bind.","commonSituations":"headscale behind nginx/caddy that already occupies 443 while listen_addr is still 0.0.0.0:443; systemd restart loop where the old process has not released the socket yet (no SO_REUSEPORT semantics); copying example configs that use :8080 into an environment where 8080 is taken; typo like '8080' instead of ':8080'.","solutions":["Find and stop the current holder of the port: ss -ltnp 'sport = :<port>' or lsof -i :<port>.","If a reverse proxy terminates TLS, point listen_addr at an internal port (e.g. 127.0.0.1:8080) and proxy to it.","For privileged ports as non-root, either run as root, use an upstream proxy, or grant the capability: setcap 'cap_net_bind_service=+ep' $(which headscale).","Confirm the address format is host:port and the host part exists on the machine (ip addr) — use 0.0.0.0:port or [::]:port deliberately."],"exampleFix":"# before\nlisten_addr: 0.0.0.0:443   # nginx already binds 443 -> EADDRINUSE\n\n# after (terminate TLS at nginx)\nlisten_addr: 127.0.0.1:8080\nserver_url: https://headscale.example.org","handlingStrategy":"validation","validationCode":"// Pre-flight: confirm the main address is bindable.\nfunc addrBindable(addr string) error {\n    l, err := net.Listen(\"tcp\", addr)\n    if err != nil { return err }\n    return l.Close()\n}","typeGuard":null,"tryCatchPattern":"if err := h.Serve(); err != nil {\n    if errors.Is(err, syscall.EADDRINUSE) && strings.Contains(err.Error(), \"binding to TCP address\") {\n        // port taken: identify with ss -ltnp, stop the holder, restart\n    }\n}","preventionTips":["Reserve one port per service; document listen_addr per host in config management.","Behind a reverse proxy, bind headscale to 127.0.0.1 on a high port.","Use setcap cap_net_bind_service for privileged ports instead of running as root."],"tags":["network","tcp","startup","configuration","ports"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}