---
title: "Troubleshooting"
description: "Solutions to common TSLink issues"
url: "https://tslink.md/docs/troubleshooting"
locale: "en"
product_version: "0.1.1"
source: "https://github.com/anydoor7/tslink/blob/v0.1.1/docs/getting-started.md"
---

> Documentation index: https://tslink.md/llms.txt · Installed binary is authoritative: `tslink manifest`.

## 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](https://login.tailscale.com/admin/settings/keys). For a running gateway, follow [credential/configuration restart management](https://tslink.md/docs/daemon.md#restarting-after-configuration-changes). 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](https://tslink.md/docs/daemon.md#restarting-after-configuration-changes), 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](https://tslink.md/docs/troubleshooting.md#node-state-directory-issues). 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](https://tslink.md/docs/people-sharing.md), 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](https://tslink.md/docs/troubleshooting.md#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](https://tslink.md/docs/services.md#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](https://login.tailscale.com/admin/dns) 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](https://tslink.md/docs/daemon.md#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](https://tslink.md/docs/daemon.md#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](https://tslink.md/docs/configuration.md#resetting-state) 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](https://tslink.md/docs/daemon.md#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](https://tslink.md/docs/configuration.md#registry-file).

## 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](https://tslink.md/docs/daemon.md#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](https://tslink.md/docs/daemon.md#restarting-after-configuration-changes) 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](https://tslink.md/docs/troubleshooting.md#authentication) for the actual mode/error.
3. Read stderr application/error logs with `tslink logs`; see [Structured Logging](https://tslink.md/docs/daemon.md#structured-logging). Any necessary restart follows [platform management](https://tslink.md/docs/daemon.md#restarting-after-configuration-changes).

## 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](https://tslink.md/docs/daemon.md#restarting-after-configuration-changes) 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](https://tslink.md/docs/experimental-roadmap.md#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](https://login.tailscale.com/admin/dns).
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](https://tailscale.com/docs/features/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](https://tslink.md/docs/troubleshooting.md#node-state-directory-issues). 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](https://tslink.md/docs/access-history.md) 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](https://tslink.md/docs/daemon.md#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](https://tslink.md/docs/access-history.md) 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](https://tslink.md/docs/daemon.md#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](https://tslink.md/docs/daemon.md#restarting-after-configuration-changes).
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](https://login.tailscale.com/admin/machines).

### 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](https://tslink.md/docs/faq.md#what-are-ephemeral-nodes) and [Tailscale's lifecycle contract](https://tailscale.com/docs/features/ephemeral-nodes). For state recovery or deliberate maintenance, preserve the full service record and follow [node-state recovery](https://tslink.md/docs/troubleshooting.md#node-state-directory-issues).

## Getting Help

The [GitHub issue tracker](https://github.com/anydoor7/tslink/issues) 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.
