Symptom: TSLink commands fail with an authentication error.
Solution: First inspect tslinkstatus--json. Default add/share can enroll without stored credentials: needs_login is a successful handoff, so open its auth_url, complete enrollment/approval, then poll tslinkurl<name>--wait. It does not require tslinklogin. For operations that require a stored credential, use an API access token (tskey-api-*) or an OAuth client secret (tskey-client-*):
bash
tslink login
If you've logged in before, your API access token may have expired. OAuth client secrets do not expire, but API-token mode is currently the most complete path for Tailscale tag/device automation. Check tslinkstatus to see your authentication state.
Symptom:tslinkserve fails with an auth error after previously working.
Solution: API access tokens expire periodically. If diagnostics identify an expired stored token, replace it through tslinklogin with a credential from Admin → Keys. For a running gateway, follow credential/configuration restart management. This is separate from a Tier 1 node's browser enrollment.
OAuth client secrets avoid periodic token expiry, but validate required tag/device operations before using them for unattended automation.
Symptom: TSLink can't read or write credentials to the system keychain.
Solution: Restore access to the expected credential provider first: unlock it and grant the TSLink binary access when prompted. An unreachable or uncertain keychain fails closed. On macOS/Linux, 0600 file fallback is allowed only after TSLink proves an old keychain value is absent or deleted; Windows has no credential-file fallback. Headless operation alone does not establish those conditions.
After resolving storage access, stdin is a secure noninteractive input method. It uses the same storage backend and does not bypass a keychain failure:
Restricted file fallback and legacy credential files may exist for compatibility, but troubleshooting guidance should avoid direct secret-file writes because shell history and pre-chmod permissions can leak credentials.
Symptom:tslinkserve exits immediately or shows an error.
Possible causes:
Another instance is already running. Check tslinkstatus--json. Default add/share already start the background gateway; complete any enrollment handoff and poll its URL. If maintenance needs a restart, use the platform-aware restart procedure, rather than starting a second gateway.
Port conflict. If the embedded tsnet node can't bind its listener, check for other Tailscale-related processes.
Network issues. The tsnet node needs internet access to connect to the Tailscale coordination server. Verify your network connection.
Invalid control URL. If using Headscale, check that your control URL is correct:
bash
tslink config get control-url
Node-state error. Inspect the actual error and follow node-state recovery. Do not delete state while a gateway or supervisor may still hold it.
Symptom: The gateway is running but you can't reach a service from another device.
Checklist:
Is the local service running? For proxy services, verify the target host:port is reachable locally:
bash
curl http://localhost:3000
Does the accessing device have a network route? Use a permitted tailnet member or an outside Tailscale account that accepted this app-device share. Check the actual login and deadline for people grants (Funnel is public).
Is the service registered? Check with tslinklist to confirm the service exists.
Does the current authorization permit access? For private HTTP/file apps, check the person's grants, app scope, deadline and revocation state, or the applicable legacy --allow login/tag rule. Registered people's grants take precedence over legacy allow rules.
Readiness and DNS. Obtain the actual ready endpoint with tslinkurl<name>--wait and check the receiving device's DNS and tailnet access policy. An HTTPS request by bare IP can fail hostname/certificate validation; it is not an equivalent URL test.
Ephemeral lifecycle. A brief disconnect does not prove deletion. Control-plane cleanup follows inactivity; inspect local status and remote node state before considering a restart or fresh enrollment. See Ephemeral Nodes Not Auto-Removing.
Symptom: Can't connect to a TCP service (database, Redis, etc.).
Checklist:
Is the local service listening? Verify the TCP target is reachable:
bash
nc -zv localhost 5432
Are you using the actual endpoint? Read tslinkurlmydb--wait. CLI registration records the target port; a manually edited entry without port falls back to 443. For the 5432 example, substitute the returned hostname:
bash
psql -h <hostname-returned-by-tslink-url> -p 5432
Client-side Tailscale running? The accessing device must have the Tailscale app running and connected.
No HTTP ACL, middleware, or headers. TCP forwards raw bytes; both CLI and registry validation reject nonempty TCP --allow / allowed_users. Use tailnet policy and the target service's own authentication and required TLS. See TCP Services.
Custom domain and ACME support is roadmap/experimental. The --domain / --acme-email flags have been removed. Old domain / acme_email registry keys are rejected with unknown_config_key even when empty; remove them. Use the default <service>.<tailnet>.ts.net hostname for shipped flows.
Symptom: TSLink can't read a directory or write to its config path.
Solutions:
Ensure the --dir path exists and is readable by your user.
Inspect ownership and permissions of the selected configuration directory before changing them. For the default path:
bash
ls -ld ~/.config/tslink ~/.config/tslink/nodes
Confirm the intended service user and directory. Before changing runtime state, complete verified shutdown; do not use a generic recursive ownership change as an unverified repair.
Symptom: A service fails to start with errors about its node state.
Solution: Each service stores its tsnet state in ~/.config/tslink/nodes/<service-name>/. An error alone does not prove corruption. Before recovery, preserve the actual complete service record from registry.json and inspect:
bash
tslink list --jsontslink status --json
Keep the original type, target/path, port, allowed_users (--allow), tags, control_url, ephemeral mode, Funnel/public acknowledgement/expiry, and all other settings. Do not replace the record with a generic proxy to port 3000: dropping --allow can broaden access.
Complete verified shutdown before touching node state, and halt on errors, warnings that leave ownership uncertain, or a running/managed gateway. Diagnose the stored error and restore the same valid service record with its original access policy. For a deliberate full reset, use configuration's guarded reset after preserving configuration; it is not a routine first-line repair. Resume through the appropriate platform workflow, complete any fresh enrollment, and verify the endpoint and access restrictions.
Preserve the original file before repair. Complete verified shutdown, then repair or restore a known-good registry with the same complete service records and access restrictions. A generic remove/re-add recipe loses those settings. Validate the repaired file before restoring managed runtime; see registry configuration.
On Unix, a five-second timeout returns an error without forcing exit; the process may still be running. Windows uses immediate process termination. Installed macOS KeepAlive can restart a stopped process. Follow verified shutdown and halt on inconclusive results. Do not delete PID records to bypass ownership checks; stop cleans stale records only after absence is confirmed.
Symptom: TSLink doesn't start when you log in after running tslinkinstall.
Solutions by platform:
macOS: Check if the LaunchAgent is loaded:
bash
launchctl list | grep tslink
Check tslinkstatus--json / tslinkdoctor--json for supervisor diagnostics. The installed plist is ~/Library/LaunchAgents/com.tslink.daemon.plist; use the verified restart procedure when repair is needed.
Symptom: The daemon starts but immediately exits. Logs may show errors about binding or authentication.
Solution:
Check for port or resource conflicts:
bash
tslink status
Inspect tslinkstatus--json / tslinkdoctor--json. Tier 1 needs_login is an enrollment handoff, not a requirement to store API credentials. Follow the authentication diagnosis for the actual mode/error.
Symptom: Adding or removing services while the gateway is running has no effect.
Possible causes:
File system events not supported. Some network-mounted or virtualized file systems do not emit fsnotify events. Inspect the actual error and use the platform-aware restart if a restart is needed.
Registry file corrupted. Validate the JSON in ~/.config/tslink/registry.json with a JSON linter.
Symptom: You updated middleware settings in registry.json but they don't take effect.
Solution: Configurable middleware is unavailable. The removed middleware registry key is rejected with unknown_config_key, even when empty. Delete the key while preserving the complete service record and existing access settings; see Roadmap Configuration Fields.
Symptom: You changed a service's type, target, or port in registry.json but the change isn't reflected.
Solution: Changes to a service's type, target, or port require the affected tsnet node to restart. The gateway handles this automatically, but the restart may take a few seconds as the node reconnects.
Rate limiting, Basic Auth, IP allow list, and CORS middleware are roadmap/experimental and have no current implementation or registry schema. The shipped protection layer is --allow for proxy/file HTTP services plus Tailscale ACL/tag policy.
Symptom: A proxy service with --funnel--public isn't accessible from the public internet.
Checklist:
Funnel enabled in Tailscale. Funnel must be enabled for your tailnet in the Tailscale admin console.
Explicit public acknowledgement. TSLink requires --public on the CLI (with or without --json), which records public_ack:true in the service's registry.json entry.
Only proxy services. Funnel only works with proxy services, not file or TCP services.
DNS propagation. The public DNS record for <service>.<tailnet>.ts.net may take a moment to propagate.
Permission to publish. The policy's nodeAttrs Funnel grant must permit the service node to publish. This is not visitor authentication: internet visitors have no tailnet caller identity check, and TSLink --allow does not protect the public endpoint. Required visitor authorization belongs to the backend application. See Tailscale Funnel.
Solution: Configurable middleware is roadmap/experimental and is not a shipped Funnel protection layer. Also note that identity headers (X-Tailscale-User-*) are not available for public Funnel requests, since the requester is not on your tailnet.
Symptom: TSLink fails to connect when using a custom control URL.
Checklist:
Verify the control URL is correct and reachable:
bash
tslink config get control-urlcurl https://headscale.example.com/health
Check that your Headscale server is running and accessible from the TSLink host.
If using per-service control URLs in registry.json, ensure each service's control_url is correct.
Before changing control-server identity or handling node state, preserve the complete service settings and follow node-state recovery. Establish verified shutdown first; do not delete a directory still held by runtime.
Docker label discovery is roadmap/experimental. There is no Docker discovery package or event watcher in the current TSLink. Register container-backed services explicitly with tslinkadd.
Prometheus /metrics is roadmap/experimental. TSLink has neither HTTP metrics instrumentation nor a scrape endpoint. If curlhttps://<service-name>.<your-tailnet>.ts.net/metrics fails, that is expected until a protected endpoint is wired.
Admin dashboard and REST API are roadmap/experimental. The current TSLink has no admin handler or dashboard; the optional tailnet-only MCP control plane is the remote management surface. Use the CLI with --json for shipped automation.
Symptom: No access log entries despite traffic flowing through TSLink services.
Solution: Use tslinkaccesslog--app<name>--since24h and check status/doctor for drops or missing history. Access history explains attested identity, privacy and gaps; tslinklogs remains daemon diagnostics.
bash
ls -la ~/.config/tslink/logs/
WhoIs-derived login / node may be empty; missing identity is not proof that access logging stopped. For the exact fields and source/cache boundaries, see Structured Logging.
Symptom: Need to understand or parse TSLink log output.
Solution: Use tslinkaccesslog--app<name>--since24h and check status/doctor for drops or missing history. Access history explains attested identity, privacy and gaps; tslinklogs remains daemon diagnostics.
Solution: Structured gateway lifecycle, warning/error, and proxy/file HTTP access entries share stderr / tslink.err.log. Daemon stdout goes to tslink.out.log and is not the structured access stream. There is no separate access.log. Filter by record type/service when investigating requests; Docker events and cluster heartbeats remain roadmap/experimental. See Log File Locations.
Symptom: Old TSLink service devices remain in the Tailscale admin console after removal.
Solution:tslinkremove always removes the local service entry. Remote tailnet cleanup is conservative: TSLink deletes a remote device only when exact ownership can be proven. If stale devices persist:
Inspect tslinkstatus--json and cleanup warnings first. Startup can attempt ownership-safe cleanup; if a restart is actually required, use platform-aware restart management.
Ensure the credential used has sufficient Tailscale API permissions (device delete access).
If TSLink reports protected candidates or skipped cleanup, manually verify and remove stale devices from the Tailscale admin console.
Symptom: Services created with --ephemeral remain in the tailnet after disconnection.
Solution: Tailscale's control plane removes ephemeral nodes after a period of inactivity, not at each disconnect. A registered/running node may still be active even without application traffic. Confirm the local ephemeral setting with tslinklist--json and the actual remote node state in the Tailscale admin console; a local flag alone does not prove remote deletion. Do not restart or re-register solely because of a short disconnect.
The GitHub issue tracker is public. Use it for non-sensitive questions or corrections. Do not post credentials, invitation links, personal data or private tailnet details in a public issue.
For collaborators preparing a report, include:
Your operating system and TSLink version (tslink--version)
The exact error message or unexpected behavior
Steps to reproduce the issue
Relevant log output from ~/.config/tslink/logs/
Redact credentials, authorization URLs, and sensitive local or caller details before sharing diagnostics.