Tailscale HTTPS shows off and cannot be disabled in Settings when local DNS fails#14746
What happened
Settings → Connections shows Tailscale HTTPS as off, although it is enabled and Tailscale Serve is exposing the backend on port 443. Turning the switch on and confirming Setup does nothing, and Settings offers no way to turn it off. This happens when the machine can't resolve its own MagicDNS name (tailscale set --accept-dns=false).
Diagnosis
The switch shows the result of a reachability probe instead of the saved setting, and the probe resolves the MagicDNS name through the OS resolver. Line numbers are from main @ 54084ae1; these files haven't changed since 99e08526.
apps/desktop/src/backend/tailscaleEndpointProvider.ts:82,93: the endpoint isavailableonly if Serve is enabled and the probe passes.packages/tailscale/src/tailscale.ts:365-381: the probe GETshttps://<MagicDNS>/.well-known/t3/environmentwith the desktop Undici client (apps/desktop/src/main.ts:216), so it goes through the OS resolver. A failure or timeout returnsfalse, and the endpoint becomesunavailable.apps/web/src/components/settings/ConnectionsSettings.tsx:3181-3183:checked={tailscaleHttpsEndpoint.status === "available"}.apps/desktop/src/settings/DesktopAppSettings.ts:326-341: re-enabling with the same port is a no-op, sorequiresRelaunch: false(apps/desktop/src/backend/DesktopServerExposure.ts:525-528).- Disable is offered only on the switch's checked → unchecked path. The endpoint-row Disable button also requires
available(ConnectionsSettings.tsx:1311-1313), and the rendered endpoint list leaves Tailscale HTTPS out anyway (:2568,:2838). - Unavailable endpoints are left out of pairing options (
ConnectionsSettings.tsx:641), and Authorized clients is shown only fornetwork-accessible || HTTPS available(:3388). Existing client authorizations are not revoked. - Graceful backend shutdown tries to remove the mapping (
apps/server/src/server.ts:683, release at:717), but the saved setting staystrue, so the next launch configures it again.
Suggested fix:
- Bind the switch to
tailscaleServeEnabled. Keep Disable reachable when discovery or the probe fails, and show probe health as a separate note. Keep Authorized clients visible when Network access or the saved Serve setting is on. Keep endpoint health checks for pairing: a savedtruemeans configured intent, so it should not mark the endpointavailableby itself. - Optional: probe a parsed Tailscale IP (
tailnetIpv4Addressesis parsed but unused) while keeping the MagicDNS name for SNI, Host and certificate checks, and validate the returned descriptor. - Regression test: saved enabled + failed or missing endpoint → Disable and client management stay reachable.
Steps to reproduce
- On Linux, join a tailnet with MagicDNS and HTTPS certificates enabled. Run
tailscale set --accept-dns=falseand check that the system resolver can't resolve this machine's<host>.<tailnet>.ts.netname. - In the desktop app, with Network access on, enable Tailscale HTTPS on port 443. After the relaunch, check that
tailscale serve statusproxies HTTPS tohttp://127.0.0.1:3773. - Reopen Settings → Connections. Tailscale HTTPS shows off.
- Turn it on and confirm Setup. The dialog closes without a relaunch and the switch stays off.
- Optional: turn Network access off while Tailscale HTTPS stays configured. Authorized clients disappears.
Expected: the switch is on with the MagicDNS URL, and turning it off removes the route (docs/user/remote-access.md: "Turn it off there to remove that route.").
Version
0.0.45-nightly.20261002.2561
Environment
Arch Linux (kernel 7.1), desktop app, Tailscale 1.98.8 with MagicDNS on tailnet-wide and accept-dns=false, Node v24.16.0 for the probe check
Evidence
| 1 | # tailscale serve status --json (host redacted) |
| 2 | {"TCP":{"443":{"HTTPS":true}},"Web":{"<host>.<tailnet>.ts.net:443":{"Handlers":{"/":{"Proxy":"http://127.0.0.1:3773"}}}}} |
| 3 | |
| 4 | # desktop-settings.json (window bounds omitted) |
| 5 | "tailscaleServeEnabled": true |
| 6 | |
| 7 | # tailscale dns status |
| 8 | Tailscale DNS: disabled. |
| 9 | MagicDNS: enabled tailnet-wide |
| 10 | |
| 11 | # resolvectl query <host>.<tailnet>.ts.net |
| 12 | resolve call failed: '<host>.<tailnet>.ts.net' does not have any RR of the requested type |
| 13 | |
| 14 | # dig +short @100.100.100.100 <host>.<tailnet>.ts.net |
| 15 | 100.x.y.z |
| 16 | |
| 17 | # curl https://<host>.<tailnet>.ts.net/.well-known/t3/environment |
| 18 | curl: (6) Could not resolve host: <host>.<tailnet>.ts.net |
| 19 | |
| 20 | # same request with --resolve <host>.<tailnet>.ts.net:443:100.x.y.z |
| 21 | http_code=200 |
| 22 | |
| 23 | # Node 24: undici fetch (same client stack as the desktop probe) and dns.lookup |
| 24 | TypeError ENOTFOUND |
| 25 | dns.lookup: ENOTFOUND |
| 26 | |
| 27 | # desktop.trace.ndjson: the probe span reports Success because the failure is recovered to false |
| 28 | {"name":"resolveTailscaleMagicDnsAdvertisedEndpoint","durationMs":432.4,"exit":{"_tag":"Success"}} |
Related issues
No open duplicate. #4919 and #4698 proposed binding the switch to the saved setting (both closed unmerged); this adds a DNS-failure repro. Related: #4028, #2830.
Fix applied or workaround
On this machine, setting "tailscaleServeEnabled": false in desktop-settings.json and running tailscale serve --https=443 off removed the route, and the setting held after restart. Quit T3 Code completely before editing the file, because the running app keeps settings in memory and rewrites the whole file. tailscale serve --https=443 off removes every web handler on port 443, not only T3's.
Untested alternative: temporarily enable Tailscale DNS. If the name resolves and the probe passes, turn HTTPS off in Settings, then restore the DNS setting.
Filed by
Claude Code (claude-opus-5-5), following .github/triage/PLAYBOOK.md but not launched through t3 triage. Findings cross-checked with Codex CLI (gpt-6.1-sol).