TSLinkTSLink Docs

Troubleshooting

Solutions to common TSLink issues

View as Markdown

Authentication

"Not logged in" Error

Symptom: TSLink commands fail with an authentication error.

Solution: First inspect tslink status --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 tslink url <name> --wait. It does not require tslink login. 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 tslink status to see your authentication state.

API Key Expired

Symptom: tslink serve fails with an auth error after previously working.

Solution: API access tokens expire periodically. If diagnostics identify an expired stored token, replace it through tslink login 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.

Keychain Access Denied

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:

bash
printf %s "$TSLINK_API_KEY" | tslink login --api-key-stdin
printf %s "$TSLINK_CLIENT_SECRET" | tslink login --client-secret-stdin

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.

Auth Key Derivation Fails

Symptom: TSLink logs show errors about deriving auth keys from the API token.

Solution: Auth keys are derived on-the-fly from your API access token via the Tailscale API. This requires:

  1. A valid, non-expired API access token.
  2. Network connectivity to the Tailscale API (api.tailscale.com).
  3. The API token must have sufficient permissions (device write access).

If using an OAuth client secret, tsnet can use the secret directly, but Tailscale REST tag/device operations still depend on API-token paths today.

Gateway

Gateway Fails to Start

Symptom: tslink serve exits immediately or shows an error.

Possible causes:

  1. Another instance is already running. Check tslink status --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.

  2. Port conflict. If the embedded tsnet node can't bind its listener, check for other Tailscale-related processes.

  3. Network issues. The tsnet node needs internet access to connect to the Tailscale coordination server. Verify your network connection.

  4. Invalid control URL. If using Headscale, check that your control URL is correct:

    bash
    tslink config get control-url
  5. 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.

Service Not Accessible

Symptom: The gateway is running but you can't reach a service from another device.

Checklist:

  1. Is the local service running? For proxy services, verify the target host:port is reachable locally:

    bash
    curl http://localhost:3000
  2. 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).

  3. Is the service registered? Check with tslink list to confirm the service exists.

  4. 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.

  5. Readiness and DNS. Obtain the actual ready endpoint with tslink url <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.

  6. 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.

TCP Service Not Working

Symptom: Can't connect to a TCP service (database, Redis, etc.).

Checklist:

  1. Is the local service listening? Verify the TCP target is reachable:

    bash
    nc -zv localhost 5432
  2. Are you using the actual endpoint? Read tslink url mydb --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
  3. Client-side Tailscale running? The accessing device must have the Tailscale app running and connected.

  4. 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.

File Service Returns 404

Symptom: A file service is running but returns 404 for all paths.

Solution: Verify the --dir path points to a valid, readable directory:

bash
ls -la /path/to/your/directory

Ensure the directory contains the files you expect to serve. File services serve directory contents directly over HTTPS.

Certificates

TLS Certificate Issues

Symptom: Browser shows certificate warnings when accessing a service.

Possible causes:

  1. First-time delay. TLS certificates are provisioned on first use and may take a few seconds. Refresh the page after a moment.

  2. HTTPS feature not enabled. Ensure HTTPS is enabled for your tailnet in the Tailscale admin console under DNS settings.

  3. Clock skew. If your system clock is significantly off, certificate validation may fail. Sync your system time.

Custom Domain Certificate Errors (Roadmap / experimental)

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.

Files and Permissions

Permission Errors

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.

Node State Directory Issues

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 --json
tslink 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.

Registry File Corruption

Symptom: TSLink refuses to start or shows JSON parse errors.

Solution: The service registry (~/.config/tslink/registry.json) must be valid JSON. Validate it:

bash
python3 -m json.tool ~/.config/tslink/registry.json

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.

Daemon

Daemon Won't Stop

Symptom: tslink stop doesn't seem to work.

Solutions:

  1. Check if the process is actually running:

    bash
    tslink status
  2. 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.

Autostart Not Working

Symptom: TSLink doesn't start when you log in after running tslink install.

Solutions by platform:

  • macOS: Check if the LaunchAgent is loaded:

    bash
    launchctl list | grep tslink

    Check tslink status --json / tslink doctor --json for supervisor diagnostics. The installed plist is ~/Library/LaunchAgents/com.tslink.daemon.plist; use the verified restart procedure when repair is needed.

  • Linux: Check the systemd user service:

    bash
    systemctl --user status tslink

    If failed, check logs and restart:

    bash
    journalctl --user -u tslink -n 50
    systemctl --user restart tslink
  • Windows: Inspect the verified Task Scheduler/supervisor state:

    powershell
    tslink status --json
    # Inspect data.supervision; Task Scheduler is the default, --startup is fallback.

Daemon Crashes on Startup

Symptom: The daemon starts but immediately exits. Logs may show errors about binding or authentication.

Solution:

  1. Check for port or resource conflicts:
    bash
    tslink status
  2. Inspect tslink status --json / tslink doctor --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.
  3. Read stderr application/error logs with tslink logs; see Structured Logging. Any necessary restart follows platform management.

Hot Reload

Hot Reload Not Working

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.

Middleware Changes Not Applying

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.

Service Type/Target/Port Changes Require Restart

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.

Middleware (Roadmap / experimental)

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.

Funnel

Funnel Not Working

Symptom: A proxy service with --funnel --public isn't accessible from the public internet.

Checklist:

  1. Funnel enabled in Tailscale. Funnel must be enabled for your tailnet in the Tailscale admin console.
  2. 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.
  3. Only proxy services. Funnel only works with proxy services, not file or TCP services.
  4. DNS propagation. The public DNS record for <service>.<tailnet>.ts.net may take a moment to propagate.
  5. 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.

Funnel with Middleware

Symptom: Middleware (rate limiting, basic auth) doesn't apply to Funnel traffic.

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.

Headscale / Custom Control Server

Can't Connect to Headscale

Symptom: TSLink fails to connect when using a custom control URL.

Checklist:

  1. Verify the control URL is correct and reachable:

    bash
    tslink config get control-url
    curl https://headscale.example.com/health
  2. Check that your Headscale server is running and accessible from the TSLink host.

  3. If using per-service control URLs in registry.json, ensure each service's control_url is correct.

  4. 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.

Mixed Control Servers

Symptom: Some services connect to the wrong control server.

Solution: TSLink supports both global and per-service control URLs:

  • Global: tslink config set control-url <url> — applies to all services without a per-service override.
  • Per-service: Set control_url in the service's entry in registry.json — overrides the global setting for that service only.

Check your global config and each service's registry entry to ensure the correct URL is used.

Docker (Roadmap / experimental)

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 tslink add.

Metrics (Roadmap / experimental endpoint)

Prometheus /metrics is roadmap/experimental. TSLink has neither HTTP metrics instrumentation nor a scrape endpoint. If curl https://<service-name>.<your-tailnet>.ts.net/metrics fails, that is expected until a protected endpoint is wired.

Admin API (Roadmap / experimental)

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.

Cluster (Roadmap / experimental)

Cluster sync is roadmap/experimental. There is no cluster implementation in the current TSLink.

Logging

Access Logs Not Appearing

Symptom: No access log entries despite traffic flowing through TSLink services.

Solution: Use tslink access log --app <name> --since 24h and check status/doctor for drops or missing history. Access history explains attested identity, privacy and gaps; tslink logs 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.

Log Format

Symptom: Need to understand or parse TSLink log output.

Solution: Use tslink access log --app <name> --since 24h and check status/doctor for drops or missing history. Access history explains attested identity, privacy and gaps; tslink logs remains daemon diagnostics.

Daemon vs Access Logs

Symptom: Confusion about which log file to check.

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.

Tailscale API Integration

Stale Devices Not Cleaned Up

Symptom: Old TSLink service devices remain in the Tailscale admin console after removal.

Solution: tslink remove 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:

  1. Inspect tslink status --json and cleanup warnings first. Startup can attempt ownership-safe cleanup; if a restart is actually required, use platform-aware restart management.
  2. Ensure the credential used has sufficient Tailscale API permissions (device delete access).
  3. If TSLink reports protected candidates or skipped cleanup, manually verify and remove stale devices from the Tailscale admin console.

Ephemeral Nodes Not Auto-Removing

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 tslink list --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.

Remote device cleanup does not remove the local service registration; it remains until tslink remove. See ephemeral node behavior and Tailscale's lifecycle contract. For state recovery or deliberate maintenance, preserve the full service record and follow node-state recovery.

Getting Help

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.

Table of Contents