{"record":{"id":"4b52fa61722cca0f","repo":"tailscale/tailscale","slug":"service-is-not-being-served-in-tun-mode","errorCode":null,"errorMessage":"service is not being served in TUN mode","messagePattern":"service is not being served in TUN mode","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/tailscale/cli/serve_v2.go","lineNumber":1617,"sourceCode":"\t}\n\tif sc.IsServingWeb(src, svcName) {\n\t\treturn fmt.Errorf(\"unable to remove; serving web, not TCP forwarding on serve port %d\", src)\n\t}\n\tsc.RemoveTCPForwarding(svcName, src)\n\treturn nil\n}\n\nfunc (e *serveEnv) removeTunServe(sc *ipn.ServeConfig, dnsName string) error {\n\tif sc == nil {\n\t\treturn nil\n\t}\n\tsvcName := tailcfg.ServiceName(dnsName)\n\tsvc, ok := sc.Services[svcName]\n\tif !ok || svc == nil {\n\t\treturn errors.New(\"service does not exist\")\n\t}\n\tif !svc.Tun {\n\t\treturn errors.New(\"service is not being served in TUN mode\")\n\t}\n\tdelete(sc.Services, svcName)\n\tif len(sc.Services) == 0 {\n\t\tsc.Services = nil // clean up empty map\n\t}\n\treturn nil\n}\n\n// cleanURLPath ensures the path is clean and has a leading \"/\".\nfunc cleanURLPath(urlPath string) (string, error) {\n\tif urlPath == \"\" {\n\t\treturn \"/\", nil\n\t}\n\n\t// TODO(tylersmalley) verify still needed with path being a flag\n\turlPath = cleanMinGWPathConversionIfNeeded(urlPath)\n\tif !strings.HasPrefix(urlPath, \"/\") {\n\t\turlPath = \"/\" + urlPath","sourceCodeStart":1599,"sourceCodeEnd":1635,"githubUrl":"https://github.com/tailscale/tailscale/blob/cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042/cmd/tailscale/cli/serve_v2.go#L1599-L1635","documentation":"removeTunServe's second guard: the service exists in sc.Services but its Tun field is false, meaning it is served via web/TCP handlers rather than TUN mode. Deleting the Services entry here would rip out its non-TUN configuration, so the CLI refuses and tells you the mode mismatch.","triggerScenarios":"Calling the TUN-off path for a service that was advertised and configured only with web/TCP handlers (svc.Tun == false). Always checked after the service-exists guard, so hitting it implies the name was right but the mode assumption was wrong.","commonSituations":"Mixed setups where some services are TUN and some are proxied; scripts assuming every service is TUN; leftover commands after migrating a service from TUN to serve handlers.","solutions":["Check how the service is actually served: `tailscale serve status --service=svc:name`","If it holds web/TCP handlers, remove those instead (e.g. `serve --https=443 off`, `--tcp=... off`)","Only use the TUN-off flow for services whose config has Tun: true"],"exampleFix":"// before\n$ # svc:myapp serves via web handlers, Tun=false\n$ tailscale --service=svc:myapp serve tun off\nservice is not being served in TUN mode\n\n// after\n$ tailscale --service=svc:myapp serve --https=443 off","handlingStrategy":"validation","validationCode":"if svc := sc.Services[svcName]; svc != nil && !svc.Tun {\n\treturn fmt.Errorf(\"%s is served via web/TCP handlers; clear those instead\", svcName)\n}","typeGuard":null,"tryCatchPattern":"if err := clearTun(svcName); err != nil {\n\tif strings.Contains(err.Error(), \"not being served in TUN mode\") {\n\t\treturn clearWebHandlers(svcName) // fall through to the right mode\n\t}\n\treturn err\n}","preventionTips":["Branch on svc.Tun from the fetched serve config to pick the right off command","Track each service's serving mode in your inventory so scripts don't assume TUN"],"tags":["tailscale","serve","tun","mode-mismatch"],"backgroundTag":null,"analyzedSha":"cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042","analyzedAt":"2026-08-15T19:58:31.583Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}