---
title: "Daemon Mode"
description: "Running TSLink as a background service with cross-platform autostart"
url: "https://tslink.md/docs/daemon"
locale: "en"
product_version: "0.1.1"
source: "https://github.com/anydoor7/tslink/blob/v0.1.1/docs/daemon-lifecycle.md"
---

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

## Overview

TSLink can run as a background daemon process, so you don't need to keep a terminal window open. It supports automatic startup on macOS, Linux, and Windows.

## Running as a Daemon

Default `tslink add` and `tslink share` already ensure the background gateway is running. Complete any `needs_login` / `auth_url` enrollment handoff, then inspect it with `tslink status --json` and wait for the service with `tslink url <name> --wait`. Running `serve` again while that gateway is running returns a conflict.

For a deliberate manual setup, first confirm there is no managed installation or running gateway. If converting an existing setup, follow [verified shutdown](https://tslink.md/docs/daemon.md#verified-shutdown) below. Register services without installing a daemon, then start the gateway yourself:

```bash
tslink add app --proxy localhost:3000 --no-daemon-install
tslink serve --daemon
```

The manual gateway runs in the background and redirects stdout/stderr to log files. It continues running after you close the terminal, but this workflow does not register login autostart or crash supervision. Complete enrollment and wait for readiness before using its services.

## Checking Status

See whether the daemon is running, authentication state, and service count:

```bash
tslink status
```

Example output:

```
→ tslink: running (pid 12345)
→ tailnet: authenticated
→ services: 3 registered
```

## Stopping the Daemon

Stop the background gateway:

```bash
tslink stop
```

On macOS/Linux, this sends `SIGTERM` and waits up to five seconds for the process to exit. A timeout returns an error without forcing termination; the process may still be running. On Windows, it requests immediate OS process termination and then waits for confirmation of exit; it does not drain requests gracefully. Check the result and `tslink status --json` before maintenance.

`stop` does not remove autostart. An installed macOS LaunchAgent can restart the process after a stop; a Linux user unit with `Restart=on-failure` remains stopped after a successful graceful exit. Use [verified shutdown](https://tslink.md/docs/daemon.md#verified-shutdown) when you need the gateway to stay stopped.

## Signal Handling

TSLink listens for OS signals to coordinate clean shutdowns:

| Signal       | Platform            | Behavior                                                    |
| ------------ | ------------------- | ----------------------------------------------------------- |
| `SIGTERM`    | Unix (macOS, Linux) | Initiate shutdown of service listeners and nodes, then exit |
| `SIGINT`     | Unix (macOS, Linux) | Same as SIGTERM (allows `Ctrl+C` in foreground mode)        |
| Process kill | Windows             | Immediate process termination via OS process kill           |

During Unix graceful shutdown, TSLink closes its control plane and shuts down each service node:

1. Cancel the node's work and stop accepting HTTP requests.
2. Give each proxy/file HTTP server up to five seconds to drain in-flight requests; if that server's shutdown fails or times out, close it.
3. Close the node's listener, tsnet node, and handler resources. Raw TCP does not use the HTTP drain step.
4. Keep service state for the next run, remove runtime/PID records, and exit.

The HTTP budget is per service node, separate from the CLI's five-second process-exit wait. Shutting down several nodes can take longer; a CLI timeout is not proof that all connections or the process have stopped.

## Autostart on Login

TSLink can register itself to start automatically when you log in:

```bash
tslink install
```

### macOS — LaunchAgent

Creates a plist at `~/Library/LaunchAgents/com.tslink.daemon.plist`.

* Starts TSLink on login
* `KeepAlive=true` — restarts automatically if the process crashes, and also after `tslink stop` while the LaunchAgent remains installed
* `ThrottleInterval=30` — throttles restart loops to 30 seconds
* Manages `stdout`/`stderr` logging
* Loaded and managed via `launchctl`

Run `tslink uninstall` before `tslink stop` when the intent is to disable autostart rather than trigger a launchd restart.

To check status after install:

```bash
launchctl list | grep tslink
```

### Linux — systemd User Service

Creates a unit file at `~/.config/systemd/user/tslink.service`.

* Runs as a user service (no root required)
* `Restart=on-failure` with `RestartSec=30` (30-second delay between restart attempts)
* `StartLimitIntervalSec=300` and `StartLimitBurst=5` limit tight failure loops
* Automatically enabled and started on install via `systemctl --user`

Without lingering, this user service starts with a user login. `loginctl enable-linger "$USER"` allows it to run after logout and start at boot without a login. If lingering was enabled only for TSLink, run `loginctl disable-linger "$USER"` after uninstall.

To check status after install:

```bash
systemctl --user status tslink
journalctl --user -u tslink -f
```

### Windows — Task Scheduler

`tslink install` registers an interactive-user scheduled task, starts TSLink immediately, and runs it again at sign-in. The task launches a built-in supervisor that restarts daemon crashes with bounded backoff. This is login-scoped operation, not an unattended boot service.

Inspect the actual supervision evidence:

```bash
tslink status --json
```

Use `data.supervision` to distinguish a verified scheduled task and live supervisor from a merely running process. If Task Scheduler is unavailable, `tslink install --startup` selects the legacy Startup script without crash recovery. Review [TSLink Windows supervision](https://github.com/anydoor7/tslink/blob/v0.1.1/docs/daemon-lifecycle.md) for migration and failure recovery.

### Removing Autostart

```bash
tslink uninstall
```

This removes the platform-specific autostart configuration. macOS/Linux also attempt to stop their managed job; a refusal, error, or warning must be resolved before treating it as removed. Windows disables the owned scheduled task, stops its supervisor and child, verifies no running instance, then deletes the task. A Startup-only fallback removes the script without owning a current process. A manually started process also needs a separate stop. Use the sequence below when the goal is to stop serving.

### Verified Shutdown

Run each step separately and inspect its result before continuing:

1. Run `tslink uninstall --json` to remove managed autostart first, avoiding macOS `KeepAlive` restarting the gateway. Halt on an error or an unverified-removal warning.
2. Run `tslink stop --json` to stop any remaining process. Halt on an error; a Unix timeout can leave the process running.
3. Run `tslink status --json`. Continue maintenance only when `ok` is true, `data.daemon_running` is false, and `data.supervision.installed`, `autostart`, and `restart_on_exit` are all false. Resolve an inconclusive result before modifying configuration or starting another gateway.

Removing autostart and stopping a process do not delete registered services or credentials.

### Restarting After Configuration Changes

For a managed setup, complete [verified shutdown](https://tslink.md/docs/daemon.md#verified-shutdown), change the global configuration or credentials, then run `tslink install` again with any previously selected install options. On macOS/Linux, install starts the managed job. On Windows, install registers the next sign-in only; to resume in the current session after confirmed shutdown, run `tslink serve --daemon` with the matching runtime options, or wait for the next sign-in. Check `tslink status --json`, complete any enrollment handoff, and use `tslink url <name> --wait` for readiness.

For a manual setup with no autostart registration, stop and verify `data.daemon_running: false` before changing settings, then restart with `tslink serve` (foreground) or `tslink serve --daemon` (background). Keep new registrations on `--no-daemon-install` if you intend to remain manual.

## Process Lifecycle

TSLink uses PID-based lifecycle management:

1. When `tslink serve --daemon` starts, it forks the process and writes its PID to `~/.config/tslink/tslink.pid`.
2. `tslink status` reads the PID file to check if the process is still running.
3. `tslink stop` reads the PID and sends a termination signal.

This ensures only one instance of the gateway runs at a time. If you try to start a second instance, TSLink will detect the existing PID and warn you that a gateway is already running.

## Structured Logging

`tslink logs` reads structured gateway diagnostics. For retained requests and access changes, use [access history](https://tslink.md/docs/access-history.md).

### Log File Locations

| File                                   | Content                                                                |
| -------------------------------------- | ---------------------------------------------------------------------- |
| `~/.config/tslink/logs/tslink.out.log` | Daemon stdout                                                          |
| `~/.config/tslink/logs/tslink.err.log` | Gateway lifecycle, warnings and errors on stderr                       |
| `~/.config/tslink/access-log/`         | Bounded asynchronous HTTP/file/TCP/guest JSONL events                  |
| `~/.config/tslink/mcp-audit.json`      | Independently bounded mutation intent/completion and lifecycle journal |

### Access Log Format

Schema-version-1 history events use typed time, kind, app/service, identity and decision fields; HTTP records add method/status/bytes/duration and sanitized path. Public Funnel does not identify a person. Query merges retained daemon segments and the journal. Missing completion means unknown outcome; drops and crash gaps prevent a complete-audit guarantee. Inspect current access-log health in status/doctor.

### Following Logs

```bash
tslink logs --source err --last 100
tail -f ~/.config/tslink/logs/tslink.err.log
tslink access log --app photos --since 24h --json
```

## Admin API and Dashboard

The admin dashboard and REST API are [roadmap/experimental](https://tslink.md/docs/experimental-roadmap.md#admin-dashboard--rest-api). Use the CLI with `--json` for shipped automation.

## Recommended Setup

For the default background workflow:

```bash
# Register each service; ordinary add ensures the gateway is running
tslink add app --proxy localhost:3000 --json
tslink add files --dir ~/shared --json
tslink add mydb --tcp localhost:5432 --json
```

Inspect each add result. If it reports `needs_login`, open its `auth_url` and complete the tailnet's enrollment/approval requirements before continuing. Then run `tslink url <name> --wait` for each service and inspect `tslink status --json`; daemon liveness alone does not establish service readiness. Do not run another `serve` or `install` as a follow-up to successful default add.

Autostart availability follows the platform's login/lingering rules above; inspect `data.supervision.autostart_scope` in `status --json` for the observed scope. Manage services with `tslink add` and `tslink remove` — registry changes apply via hot reload without restarting the gateway.
