TSLinkTSLink 文档

管理服务

如何添加、移除和管理代理、文件和 TCP 服务

查看 Markdown

概述

TSLink 中的"服务"是一个命名条目,将 Tailscale 网络上的主机名映射到本地目标。TSLink 支持三种服务类型:

类型标志用途
代理--proxy反向代理到 Web 服务器
文件--dir通过 HTTPS 提供目录服务
TCP--tcp原始 TCP 转发(数据库、自定义协议)

每个服务使用独立 tsnet 节点,拥有自己的 tailnet 身份和主机名,可由 tailnet 策略按服务控制访问。代理和文件服务使用带 TLS 证书的 HTTPS,例如 https://<名称>.<tailnet>.ts.net;原始 TCP 使用主机名和端口,例如 <名称>.<tailnet>.ts.net:5432,没有 TSLink HTTP/TLS 监听器。请读取实际运行时端点,不要自行拼接主机名。

代理服务

使用 --proxy 通过反向代理暴露本地 Web 服务:

bash
tslink add <名称> --proxy <host:port>

示例:

bash
# 暴露 Next.js 开发服务器
tslink add frontend --proxy localhost:3000

# 暴露运行在自定义端口的 API
tslink add api --proxy 127.0.0.1:8080

# 简写形式 — 省略 host 表示 localhost
tslink add myapp --proxy :3000

代理身份传递是 best-effort:TSLink 先移除客户端提交的 X-Tailscale-* 头,只有 Tailscale WhoIs 解析出用户资料时才添加身份头。未配置 --allow 时,查询失败仍可能转发请求且不带身份头;配置 --allow 后,缺少身份或未获授权的请求会在到达后端前被拒绝。

只有在受控的 TSLink 到后端路径中、防止直接绕过代理时,才能信任身份头。应用若要求用户身份,就应拒绝缺失值,并保留所需认证与授权。文件服务不注入代理身份头。头字段、后端 Host 重写及转发头重建见反向代理。

后端错误: 非超时的上游失败返回 502 Bad Gateway,超时返回 504 Gateway Timeout。错误正文中的 “Service unavailable” 不表示 HTTP 状态为 503。

文件服务

使用 --dir 通过 HTTPS 共享目录:

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

目录以只读方式通过内置文件浏览器提供服务,并启用目录列表。支持绝对路径和相对路径;相对路径在注册时解析。

目录在该服务主机名的根路径提供;用 tslink url docs --wait 获取实际地址。已配置的 --allow 和 HTTP 访问日志可以使用 WhoIs,但文件处理器不注入代理身份头。

TCP 服务

使用 --tcp 暴露原始 TCP 连接(数据库、Redis、自定义协议):

bash
# 暴露 PostgreSQL 数据库
tslink add mydb --tcp localhost:5432

# 暴露 Redis
tslink add cache --tcp localhost:6379

TCP 服务执行双向字节转发,并正确处理 TCP 半关闭 — 任何基于 TCP 的协议都可以使用。CLI 会从 --tcp host:port 记录目标端口,tailnet listener 使用该端口。如果手工编辑 registry 且省略 port,runtime 会回退到 443。

TCP 服务不经过 HTTP middleware、身份头或 HTTP --allow。CLI 注册及 registry 验证均拒绝 TCP 非空 allowed_users / --allow。用 tailnet 策略和目标服务自身认证保护 TCP。

Tailnet 流量由 WireGuard 加密,但 TSLink 不为原始 TCP 添加应用 TLS。认证及需要的应用/后端 TLS,应在目标服务和客户端中配置。

常见用途:

  • 数据库: PostgreSQL、MySQL、MongoDB
  • 缓存: Redis、Memcached
  • SSH 隧道: 通过 tailnet 转发 SSH 连接

注册并就绪后,从 tailnet 策略允许的设备访问 tslink url <name> --wait 返回的实际主机名和端口。以下客户端示例使用主机名占位值:

bash
# PostgreSQL
psql -h <mydb-实际返回的主机名> -p 5432

# SSH
ssh -p 22 <已注册-SSH-服务返回的主机名>

高级标志

这些标志可以与任何服务类型组合使用(除非另有说明):

临时节点

为临时服务请求临时节点:

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

Tailscale 控制面在不活跃后清理节点;短暂断连不证明设备已移除。本地服务注册保留至显式移除,见临时节点行为。

ACL 标签

有存储凭据时配置服务的节点标签,前提是已有标签所有权,或显式授权普通 ACL 配置:

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

即使命令将标签存进 registry,默认无存储凭据(Tier 1)节点仍归用户所有且无标签。保存标签不会改变远端策略。API-token 模式按服务实际标签派生认证材料;client-secret 操作更窄。普通 tag-owner ensure 要求在 login / serve 上显式传 --manage-acl,并拥有足够策略权限。默认标签、Tier 1 保留注册状态及独立的已确认 Funnel 配置流程见标签配置。

访问控制

将代理/文件 HTTP 服务限制为 Tailscale 登录邮箱或设备标签:

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

配置 --allow 后,缺少或不匹配的 WhoIs 身份会在到达代理后端或文件内容前收到 403 Forbidden。解析按源 IP 缓存 60 秒,不是每次请求都重新查询控制面。文件服务通过 WhoIs 授权/记录日志,不注入身份头;保留应用自身所需授权。

这是 HTTP 请求过滤,不适用于原始 TCP 服务。

人员授权为私有 HTTP/文件添加逐请求 WhoIs 检查与期限。已管理账户的人员授权优先于旧 allow,撤销保留拒绝记录。仅旧 allow 检查及身份头使用上面的缓存行为;人员授权不缓存。见分享给指定的人。

Tailscale Funnel

通过 Tailscale Funnel 将代理服务公开暴露到互联网:

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

此处的开放 Funnel 发布仅适用于代理服务。它必须显式传 --public 确认,会让服务 URL 公开可访问,客户端无需安装 Tailscale,且不受 TSLink 身份执行保护。允许节点公开服务不等于授权互联网访客;所需访客认证与授权应由后端应用负责。

自定义域名(Roadmap / experimental)

自定义域名 TLS 和 ACME 尚未实现。--domain / --acme-email 标志及 domain / acme_email registry 字段已移除;旧 registry 键即使为空也会以 unknown_config_key 拒绝。已交付流程请使用默认 <service>.<tailnet>.ts.net 主机名。

服务级控制服务器

覆盖全局控制服务器(适用于混合使用 Tailscale 和 Headscale 节点的场景):

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

或直接在 registry.json 中设置:

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

配置优先级: 服务级标志/配置 > serve 的 CLI 标志 > 全局配置(tslink config)> Tailscale 默认值。

中间件(Roadmap / experimental)

可配置 rate limiting、Basic Auth、IP allow list 和 CORS 尚不可用,已移除的 middleware 键即使为空也会在 registry 加载时以 unknown_config_key 拒绝,见实验性与路线图。它不是运行中分享的保护层。

Docker 自动发现(Roadmap / experimental)

Docker discovery 尚未实现,当前 TSLink 没有 discovery 包或可用标签。该功能接入并完成集成测试前,请用 tslink add 显式注册服务。

命名约定

服务名称是 URL 的一部分:

代码
https://<服务名>.<tailnet名>.ts.net

规则:

  • 仅限小写字母、数字和连字符
  • 必须以字母或数字开头和结尾
  • 在 TSLink 实例中必须唯一

好的名称示例: webapp、api-v2、staging-server、docs

列出服务

查看所有已注册的服务:

bash
tslink list

显示每个条目的服务名称、类型、目标和已配置的标志。

移除服务

按名称移除服务:

bash
tslink remove frontend

本地移除会删除 registry 条目;运行中的网关通过热重载检测到变化后停止服务。远端 tailnet 清理只有在 TSLink 能证明匹配设备的精确所有权时才会尝试;否则匹配候选会报告为 protected,并跳过清理。本地成功不证明远端设备已删除。

热重载

TSLink 使用 fsnotify 监控 registry.json 的变化。当你在网关运行时添加或移除服务:

  1. 注册表文件自动更新。
  2. 文件监控器检测到变化。
  3. 新服务完成入网、审批并就绪后开始接收流量;移除的服务停止。

这意味着你可以在不中断其他活动服务的情况下管理服务。

通过热重载生效:

  • 添加新服务(tslink add)
  • 移除服务(tslink remove)
  • runtime 能检测到的结构性变化,例如 target/path/port、标签、临时节点模式或服务级控制服务器 URL 更新,会重启受影响节点

Runtime 重启不同于注册状态重置:标签修改会保留 Tier 1 用户注册。字段及身份契约见热重载。

需要重启网关:

  • 修改全局配置(如 control-url)
  • 更新认证凭证或切换凭证模式(tslink login)
  • 修改旧版 authkey 文件

这些变更请使用按平台恢复运行的重启流程。受管理安装需先确认停机,再通过对应管理器恢复运行;手动 stop / serve 仅适用于未登记自启动的环境。

启动时,TSLink 还会尝试对上次运行遗留的过期 tailnet 节点做所有权安全的清理。缺少精确所有权证明的候选会报告为 protected/skipped。

多服务

注册任意数量的服务。每个服务获得自己的主机名:

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

默认 add 已会确保后台网关运行。检查每次结果,如返回 needs_login,打开它的 auth_url 并完成 tailnet 要求的注册/审批。随后对每个服务运行 tslink url <name> --wait,并检查 tslink status --json。同一网关会同时运行所有已就绪服务;默认 add 后不要再执行 serve。

明确选择手动运行时,注册使用 --no-daemon-install,且只有在没有网关或受管理安装时才启动 serve。该流程及确认停机的前提见守护进程模式。

访客与私有管理流程

上面的 Funnel 注册是开放发布。访客链接则从私有 HTTP proxy 应用开始,明确启用有强制 gate 和可选 PIN 的公网 Funnel。链接分别到期,私有人员/allow 检查保持独立。新公开期限至少 1h,默认 24h,默认上限 7d,拒绝 never。期限说明主人上限与明确 regrant。

每台主机的私有 portal 与请求帮助符合条件的 tailnet 成员发现或申请私有 HTTP/文件应用。访问历史检查保留的访问,不保证完整记录。

目录