---
title: "Managing Services"
description: "How to add, remove, and manage proxy, file, and TCP services"
url: "https://tslink.md/docs/services"
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`.

## Overview

A "service" in TSLink is a named entry that maps a hostname on your Tailscale network to a local target. Each service runs as a separate tsnet node with its own tailnet identity and hostname, so tailnet policy can control access per service. TSLink supports three service types:

| Type      | Flag      | Use Case                                         |
| --------- | --------- | ------------------------------------------------ |
| **Proxy** | `--proxy` | Reverse proxy to a web server                    |
| **File**  | `--dir`   | Serve a directory over HTTPS                     |
| **TCP**   | `--tcp`   | Raw TCP forwarding (databases, custom protocols) |

Proxy and file services use HTTPS with a TLS certificate, such as `https://<name>.<tailnet>.ts.net`. Raw TCP uses a hostname and port, such as `<name>.<tailnet>.ts.net:5432`, without a TSLink HTTP/TLS listener. Read the actual endpoint from runtime output rather than constructing a hostname.

## Proxy Services

Use `--proxy` to expose a local web service via reverse proxy:

```bash
tslink add <name> --proxy <host:port>
```

**Examples:**

```bash
# Expose a Next.js dev server
tslink add frontend --proxy localhost:3000

# Expose an API running on a custom port
tslink add api --proxy 127.0.0.1:8080

# Short form — omit host for localhost
tslink add myapp --proxy :3000
```

Proxy identity propagation is best-effort: TSLink strips client-provided `X-Tailscale-*` headers and adds identity headers only when Tailscale WhoIs resolves a user profile. Without `--allow`, lookup failure can still forward a request with no identity headers; with `--allow`, missing or unauthorized identity is denied before the backend.

Trust identity headers only across a controlled TSLink-to-backend path that prevents direct bypass. Applications requiring user identity must reject missing values and keep their required authentication and authorization. File services do not inject proxy headers. The header fields, backend `Host` rewrite, and reconstructed forwarding headers are documented in [Reverse Proxy](https://tslink.md/docs/architecture.md#reverse-proxy).

**Backend errors:** A non-timeout upstream failure returns `502 Bad Gateway`; a timeout returns `504 Gateway Timeout`. The error response's “Service unavailable” text does not mean status 503.

## File Services

Use `--dir` to share a directory over HTTPS:

```bash
tslink add docs --dir ~/Documents/shared
```

The directory is served read-only with a built-in file browser and directory listing enabled. Both absolute and relative paths are supported; relative paths are resolved at registration time.

The directory is served at the root of its service hostname; read its exact address with `tslink url docs --wait`. Configured `--allow` and HTTP access logging can use WhoIs, but this file handler does not inject proxy identity headers.

## TCP Services

Use `--tcp` to expose raw TCP connections (databases, Redis, custom protocols):

```bash
# Expose a PostgreSQL database
tslink add mydb --tcp localhost:5432

# Expose Redis
tslink add cache --tcp localhost:6379
```

TCP services perform bidirectional byte forwarding with proper TCP half-close handling — any TCP-based protocol works. The CLI stores the target port from `--tcp host:port`, and the tailnet listener uses that port. If a manually edited registry entry omits `port`, runtime falls back to `443`.

TCP services do not run through HTTP middleware, identity headers, or HTTP `--allow`. Both CLI registration and registry validation reject nonempty TCP `allowed_users` / `--allow`. Protect TCP with tailnet policy and the target service's own authentication.

Tailnet traffic is encrypted with WireGuard, but TSLink does not add application TLS to raw TCP. Configure authentication and any application/backend TLS in the target service and its clients.

**Common use cases:**

* **Databases:** PostgreSQL, MySQL, MongoDB
* **Caches:** Redis, Memcached
* **SSH tunneling:** Forward SSH connections through your tailnet

After enrollment/readiness, use the actual hostname and port from `tslink url <name> --wait` from a device permitted by tailnet policy. These client examples use placeholders for that hostname:

```bash
# PostgreSQL
psql -h <hostname-returned-for-mydb> -p 5432

# SSH
ssh -p 22 <hostname-returned-for-an-ssh-service>
```

## Advanced Flags

These flags can be combined with any service type unless noted otherwise.

### Ephemeral Nodes

Request an ephemeral node for a temporary service:

```bash
tslink add devserver --proxy :8080 --ephemeral
```

Tailscale's control plane cleans it up after inactivity; a brief disconnect is not proof of removal. The local service registration remains until explicitly removed. See [ephemeral node behavior](https://tslink.md/docs/faq.md#what-are-ephemeral-nodes).

### ACL Tags

In stored-credential mode, configure the service's node tags, with existing tag ownership or explicitly authorized ordinary ACL provisioning:

```bash
tslink add api --proxy :8080 --tags tag:api,tag:prod
```

Default credential-free (Tier 1) nodes stay user-owned and untagged even if this command saves tags in the registry. Saving tags does not change remote policy. API-token mode derives auth material with effective service tags; client-secret operations are narrower. Ordinary tag-owner ensure requires explicit `--manage-acl` on `login` / `serve` and sufficient policy permissions. See [Tag Configuration](https://tslink.md/docs/configuration.md#tag-configuration) for default tags, retained Tier 1 enrollment, and the separate acknowledged Funnel provisioning flow.

### Access Control

Restrict a proxy/file HTTP service to Tailscale login emails or device tags:

```bash
tslink add internal --proxy :9090 --allow user@example.com,tag:admin
```

When `--allow` is configured, missing or non-matching WhoIs identity receives `403 Forbidden` before the proxy backend or file content. Resolution uses a 60s source-IP cache; it is not a fresh control-plane query on every request. File services authorize/log using WhoIs but do not inject identity headers. Preserve the application's own required authorization.

This is an HTTP request filter. It is not available for raw TCP services.

People grants add per-request WhoIs checks and deadlines for private HTTP/files. Managed logins use their people grants ahead of legacy allow rules; revocation keeps a deny record. The legacy-only allow check and identity headers have the cache behavior above; people authorization is not cached. See [share with a person](https://tslink.md/docs/people-sharing.md).

### Tailscale Funnel

Expose a proxy service publicly on the internet via [Tailscale Funnel](https://tailscale.com/kb/1223/funnel):

```bash
tslink add public-site --proxy localhost:3000 --funnel --public
```

This open Funnel publication is only available for proxy services. It requires explicit `--public` acknowledgement, makes the service URL publicly accessible without requiring Tailscale on the client, and is not covered by TSLink identity enforcement. Permission to publish does not authorize internet visitors; keep required visitor authentication and authorization in the backend application.

### Custom Domains (Roadmap / experimental)

Custom-domain TLS and ACME are not implemented. The `--domain` / `--acme-email` flags and `domain` / `acme_email` registry fields have been removed; old registry keys are rejected with `unknown_config_key` even when empty. Use the default `<service>.<tailnet>.ts.net` hostname for shipped flows.

### Per-Service Control URL

Override the global control server for a specific service (useful when mixing Tailscale and Headscale nodes):

```bash
tslink add headscale-app --proxy localhost:3000 --control-url https://headscale.example.com
```

Or set it directly in `registry.json`:

```json
{
  "name": "headscale-app",
  "type": "proxy",
  "target": "http://localhost:3000",
  "control_url": "https://headscale.example.com"
}
```

**Configuration priority:** per-service flag/config > CLI flag on `serve` > global config (`tslink config`) > Tailscale default.

### Middleware (Roadmap / experimental)

Configurable rate limiting, Basic Auth, IP allow lists, and CORS are unavailable; the removed `middleware` key is rejected with `unknown_config_key` at registry load, even when empty. See [Experimental & Roadmap](https://tslink.md/docs/experimental-roadmap.md#middleware). It is not a protection layer for a running share.

## Docker Auto-Discovery (Roadmap / experimental)

Docker discovery is not implemented; the current TSLink has no discovery package or accepted labels. Register services explicitly with `tslink add` until this feature is wired and integration-tested.

## Naming Conventions

Service names become part of the URL:

```
https://<service-name>.<tailnet-name>.ts.net
```

**Rules:**

* Lowercase letters, numbers, and hyphens only
* Must start and end with a letter or number
* Must be unique within your TSLink instance

**Good names:** `webapp`, `api-v2`, `staging-server`, `docs`

## Listing Services

View all registered services:

```bash
tslink list
```

Shows service name, type, target, and configured flags for each entry.

## Removing a Service

Remove a service by name:

```bash
tslink remove frontend
```

Local removal deletes the registry entry; a running gateway stops the service after detecting that change through hot reload. Remote tailnet cleanup is attempted only when TSLink can prove exact ownership of a matching device; otherwise matching candidates are reported as protected and cleanup is skipped. Local success does not prove remote device deletion.

## Hot Reload

TSLink watches `registry.json` for changes using [fsnotify](https://github.com/fsnotify/fsnotify). When you add or remove a service while the gateway is running:

1. The registry file is updated automatically.
2. The file watcher detects the change.
3. New services start receiving traffic once enrollment, approval, and readiness are complete; removed services stop.

This means you can manage services without interrupting other active services.

**What takes effect through hot reload:**

* Adding a new service (`tslink add`)
* Removing a service (`tslink remove`)
* Structural changes that runtime detects, such as target/path/port, tags, ephemeral mode, or per-service control URL updates, by restarting the affected node

Runtime restart is distinct from enrollment reset: tag edits retain Tier 1 user enrollment. See [Hot Reload](https://tslink.md/docs/configuration.md#hot-reload) for the field and identity contract.

**What requires a gateway restart:**

* Changing global configuration (e.g., `control-url`)
* Updating authentication credentials or swapping credential modes (`tslink login`)
* Changing the legacy `authkey` file

Use the [platform-aware restart sequence](https://tslink.md/docs/daemon.md#restarting-after-configuration-changes) for these changes. A managed installation needs verified shutdown and restoration through its manager; a manual `stop` / `serve` sequence is only for a setup without autostart registration.

On startup, TSLink also attempts ownership-safe stale tailnet node cleanup for previous runs. Candidates without exact ownership proof are reported as protected/skipped.

## Multiple Services

Register as many services as you need. Each gets its own hostname:

```bash
tslink add app --proxy localhost:3000
tslink add api --proxy localhost:8080
tslink add docs --dir ~/docs
tslink add mydb --tcp localhost:5432
```

Default add already ensures the background gateway is running. Inspect each result; if it reports `needs_login`, open its `auth_url` and complete the tailnet's enrollment/approval requirements. Then run `tslink url <name> --wait` for each service and inspect `tslink status --json`. The same gateway serves all ready services; do not run `serve` again after default add.

For an explicitly manual workflow, use `--no-daemon-install` on registrations and start `serve` only when no gateway or managed installation is present. See [daemon mode](https://tslink.md/docs/daemon.md#running-as-a-daemon) for that workflow and the verified shutdown prerequisite.

## Guest and private management workflows

The Funnel registration above is an **open publication**. [Guest links](https://tslink.md/docs/guest-links.md) instead start with a private HTTP proxy app and explicitly enable public Funnel with a mandatory guest gate and optional PIN. Each link expires; private people/allow checks remain independent. New public lifetime is at least 1h, defaults to 24h and has a default 7d maximum; never is refused. [Durations](https://tslink.md/docs/durations.md) describes owner-configured limits and explicit regrant.

Use the [private per-host portal and requests](https://tslink.md/docs/portal-requests.md) to help eligible tailnet members find or request private HTTP/file apps. Use [access history](https://tslink.md/docs/access-history.md) to inspect retained access, not as a completeness guarantee.
