说明 这教程全网应该是唯一的 内网部署。
因为太实用了,不管是回家,还是建立私有隧道,或者是添加节点等等吧都行,不过也有硬性要求,就是家里要使用pve或其它虚拟系统,软路由是ros(其它的路由系统道理也是相通的)才可以部署!
pve下新建一个vm虚拟机 ubuntu 24.04为例:
目录结构(需要建立4个容器)
都装在同一台机器上,内网ip例如:10.20.20.8
~/data/docker_data/
├── headscale及headscale-UI/ # 服务端与配置
├── derps/ # 中继服务器
└── tailscale/ # 网关节点
|—— Nginx Proxy Manager / # 反向代理服务 后面引用会简称npm域名只供参考,只是举例 ,域名可以在内网搭建好npm容器后,通过npm申请证书,下面有教程。
| Domain | Forward IP | Forward Port |
|---|---|---|
| https://hs.jgaga.xyz:12335 | 10.20.20.8 | 8080 (Headscale 容器对外服务端口) |
| https://derps.jgaga.xyz:12335 | 10.20.20.8 | 8082 (DERP 容器对外服务端口) |
| https://hs-ui.jgaga.xyz:12335 | 10.20.20.8 | 9443 (web页后台管理端口) |
| http://10.20.20.8 | 10.20.20.8 | 81 (Nginx Proxy Manage后台管理端口) |
开始安装
docker一键安装
curl -fsSL https://raw.githubusercontent.com/hanigege/scripts/main/docker.sh -o docker.sh && sh docker.shdocker.sh 该脚本用于自动安装 docker 以及 docker 插件(composes、buildx 等), 同时该脚本将会自动配置 /etc/docker/daemon.json 文件, 目前针对 docker daemon 的配置逻辑如下: 默认开启 init 支持, 防止容器产生僵尸进程 切换 docker 存储目录为 /data/docker, 此后所有 docker 数据将会存放于此 限制容器日志文件大小, 单个容器最多 5 个日志文件, 单个日志文件最大 10m, 防止容器 stdout 过量输出导致占满磁盘空间 开启 live-restore 防止 docker daemon 意外重启导致容器重启 Debian、Redhat 系列系统默认 CGroups Driver 切换为 systemd, Alpine 系统保持默认 cgroupfs v2 该脚本安装完成后默认会 Pin 住 docker 相关软件包, 防止自动升级导致容器重启或出现不可逆故障.
如果是linux下的用普通用户操作的,请记的添加到docker组:
- 一步解决
把用户加入 docker 组:
sudo usermod -aG docker $USER然后 重新登录一次系统(很重要)。
如果是 SSH:
exit再重新登录。
然后再测试
docker ps如果成功,会显示:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES1. 宿主机底层网络与内核准备 (TUN & iptables)
在部署 Tailscale 容器前,必须确保宿主机内核支持虚拟网卡和底层防火墙路由。
针对 Alpine 系统(当前环境): Alpine 内核精简,需手动加载模块并设置开机自启。在宿主机终端执行:
Bash
加载虚拟网卡与防火墙模块
modprobe tun
modprobe ip_tables
modprobe iptable_filter
modprobe iptable_nat写入开机自启名单
echo "tun" >> /etc/modules
echo "ip_tables" >> /etc/modules
echo "iptable_filter" >> /etc/modules
echo "iptable_nat" >> /etc/modules开启 IPv4 转发
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
sysctl -p针对 Ubuntu 系统(迁移备用): Ubuntu 默认已内置并加载了所需的 tun 和 iptables 模块,无需手动 modprobe。仅需开启内核 IP 转发即可:
# 开启 IPv4 转发
echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p2. Tailscale docker-compose.yml (双系统通用)
此配置在 Alpine 和 Ubuntu 下均可完美运行。保留了 /lib/modules 映射以防部分精简版 Linux 动态调用模块失败。
- 建立目录
mkdir -p ~/data/docker_data/tailscale
cd ~/data/docker_data/tailscale- 建立docker-compose.yaml
nano docker-compose.yamlservices:
tailscale:
image: tailscale/tailscale:latest
container_name: tailscale-node
hostname: home-gateway
restart: always
network_mode: "host" # 必须使用 host 模式获取真实物理网卡权限
cap_add:
- NET_ADMIN
- NET_RAW
devices:
- /dev/net/tun:/dev/net/tun
volumes:
- ./tailscale-state:/var/lib/tailscale
- /lib/modules:/lib/modules:ro # 允许容器读取宿主机内核模块(对 Alpine 必须,对 Ubuntu 无害)
environment:
- TS_STATE_DIR=/var/lib/tailscale
- TS_USERSPACE=false # 禁用用户态,强制使用内核驱动建立物理级路由
# 强制监听固定入站端口,为外部 P2P 打洞提供明确入口
command: tailscaled --port=78293. derper中继容器安装
- 建立目录
mkdir ~/data/docker_data/derps
cd ~/data/docker_data/derps- 建立docker-compose.yaml
nano docker-compose.yamlservices:
derper:
image: fredliang/derper
container_name: derper
restart: always
ports:
- "8082:80" # 专门用于接收 NPM 的反代流量
# 核心修正:保持后端 HTTP 监听,坚决关闭 STUN 避免内网 IP 污染
command: /app/derper --a :80 --http-port 80 --stun=false --verify-clients=false
environment:
- DERP_ADDR=:80
- DERP_HTTP_PORT=80VERIFY_CLIENTS=false# 暂设为false以确保连接成功(意思是不用认证了)
4. headscale容器安装(包含Headscale UI WEB管理容器)
- 建立目录
mkdir -p ~/data/docker_data/headscale
cd ~/data/docker_data/headscale- 建立docker-compose.yaml
nano docker-compose.yamlservices:
# 服务 1:Headscale 主程序
headscale:
image: headscale/headscale:latest
container_name: headscale-server
restart: always
volumes:
- ./config:/etc/headscale
- ./data:/var/lib/headscale
ports:
- "8080:8080"
command: serve
# 服务 2:Headscale UI 界面 (注意:现在它和上面的 headscale 对齐了)
headscale-ui:
image: ghcr.io/gurucomputing/headscale-ui:latest
container_name: headscale-ui
restart: always
# 建议映射一个端口,方便你在 NPM 还没配好时先用内网 IP 验证一下
ports:
- "9443:8080"Headscale 服务端配置 (config.yaml)
文件就位:
将下面的 config.yaml 和 derp.yaml 放入 ~/data/docker_data/headscale/config/
# 进入目录
cd ~/data/docker_data/headscale/config
nano config.yaml- 添加内容:
# Headscale 容器版完整配置 (更改你的域名)
server_url: https://hs.jgaga.xyz:12335
# 容器内必须监听 0.0.0.0 才能接收来自 NPM 的转发
listen_addr: 0.0.0.0:8080
metrics_listen_addr: 0.0.0.0:9090
grpc_listen_addr: 0.0.0.0:50443
grpc_allow_insecure: false
noise:
# 路径映射到容器内的 /var/lib/headscale
private_key_path: /var/lib/headscale/noise_private.key
prefixes:
v4: 100.64.0.0/10
allocation: sequential
derp:
server:
enabled: false # 独立容器运行 DERP,此处关闭
automatically_add_embedded_derp_region: false
urls: [] # 禁用官方节点,专注自建
paths:
- /etc/headscale/derp.yaml # 映射在 config 目录下的文件
auto_update_enabled: true
update_frequency: 1h
database:
type: sqlite
sqlite:
path: /var/lib/headscale/db.sqlite
write_ahead_log: true
# 日志级别设为 info,确保能看到 DERP 加载记录
log:
level: info
format: text
dns:
magic_dns: false
base_domain: i.love.you
override_local_dns: true
# 下面更改为你的内网的 DNS
nameservers:
global:
- 10.20.20.6. # 优先级最高,所有解析全部扔进隧道,交给内网 MosDNS 处理
- 1.1.1.1 # 救命稻草,负责在隧道断开时解析控制端域名
unix_socket: /var/run/headscale/headscale.sock
unix_socket_permission: "0770"
randomize_client_port: false
taildrop:
enabled: true** 上面配置需要更改两处,注释里已经注明,注意查看(域名、内网dns)
Headscale 服务端配置 (derp.yaml)
# 进入目录
cd ~/data/docker_data/headscale/config
nvim derp.yaml路径: ~/data/docker_data/headscale/config/derp.yaml
关键点: derpport 必须填手机从公网访问的端口(12335)。
Headscale derp.yaml 终态配置 将 TCP 流量中转与 UDP 探测彻底分离,防止内网网关 IP 污染外部设备的 STUN 寻址。
regions:
900:
regionid: 900
regioncode: Gary
regionname: Gary Home Network
nodes:
- name: 900a
regionid: 900
hostname: derps.jgaga.xyz
derpport: 12335
stunport: -1
stunonly: false
derponly: true
- name: 900b
regionid: 900
hostname: stun.miwifi.com
stunport: 3478
stunonly: true
derponly: false- 上面的配置中derps.jgaga.xyz改为你的域名就可以了,下面的域名不要动是第三方公共的
Headscale 服务端配置 (db.sqlite)建立一个数据库
新建一个:
# 创建配置目录
mkdir -p ~/data/docker_data/headscale/config
# 进入目录
cd ~/data/docker_data/headscale/config
# 创建一个空白的数据库文件
touch db.sqlite4. Nginx Proxy Manager容器安装
- 建立目录
mkdir ~/data/docker_data/npm
cd ~/data/docker_data/npm- 建立docker-compose.yaml
nano docker-compose.yamlservices:
app:
image: 'docker.io/jc21/nginx-proxy-manager:latest'
restart: unless-stopped
ports:
- '12332:80'
- '81:81'
- '12335:443'
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt- 说明:对外的https端口为12335,
内网管理端口为81对外80端口为12332 - 登录Nginx Proxy Manage管理平台:
http://10.20.20.8:81(改你的ip)
配置Nginx Proxy Manager
- Nginx Proxy Manager里的域名下图这块,先自行建立好:不会可以看咕咕的教学视频

npm上的域名反向配置,可以绑定docker接口的ip或者是内网ip:10.20.20.8
| Domain | 内网 IP | 端口 |
|---|---|---|
| hs.jgaga.xyz | 172.17.0.1 | 8080 |
| derps.jgaga.xyz | 172.17.0.1 | 8082 |
| hs-ui.jgaga.xyz | 172.17.0.1 | 9443 |
下面的配置选项都照下图开启就可以了

Websockets Support 必须开启 长连接支持

SSL开启,支持https
Ros篇
都配置好以后,后面需要手动做好ros的端口映射、回流,(本站有这方面的教程)
即:tailscale打洞的的端口7829的nat转发,及npm的对外的https端口(tcp)12335
4. RouterOS (ROS) 核心防火墙恢复指令
确保外网探测包和内网回环(Hairpin NAT)均能准确到达 10.20.20.8 宿主机。在 ROS 终端直接粘贴执行:
代码段
# 放行 Tailscale 固定入站端口 (核心打洞入口)
/ip firewall nat add action=dst-nat chain=dstnat comment="Tailscale_Inbound_Fixed" dst-port=7829 protocol=udp dst-address-type=local to-addresses=10.20.20.8 to-ports=7829
# 放行 DERP TCP/UDP 中转端口 npm的对外的https端口
/ip firewall nat add action=dst-nat chain=dstnat comment="https-tcp" dst-port=12335 protocol=tcp dst-address-type=local to-addresses=10.20.20.8 to-ports=12335(注:前提是 ROS 全局 masquerade 伪装规则已正常配置,保障内网设备回环与正常出海)
导入后,如下图,关注导入规则的位置,都放最上面就行了!

derp域名服务及headscale域名服务的优化(必须操作)在npm中,下面的域名的配置里添加以下:

derps.jgaga.xyz 高级中填写:
location / {
proxy_pass http://10.20.20.8:8082;
proxy_http_version 1.1;
# --- 关键修正:去掉端口号,仅透传域名 ---
# 这样可以匹配 DERP 容器内部的证书/域名校验
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# --- 保持长连接稳定性 ---
proxy_buffering off;
proxy_request_buffering off;
proxy_socket_keepalive on;
tcp_nodelay on;
# 超时设置,防止感叹号
proxy_connect_timeout 60s;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
# --- 必须:WebSocket 协议升级 ---
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}hs.jgaga.xyz 高级中填写:
# 1. 针对 UI 的 API 请求,允许跨域
location ~ ^/(admin|web|api) {
proxy_pass http://10.20.20.8:8080;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
# 跨域头只在这些路径生效
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE" always;
add_header Access-Control-Allow-Headers * always;
if ($request_method = OPTIONS) {
return 204;
}
}
# 2. 针对手机客户端的 Noise 协议通讯,保持纯净(解决 Out of sync)
location / {
proxy_pass http://10.20.20.8:8080;
proxy_http_version 1.1;
# 核心:必须使用动态 Upgrade,不要硬编码 Connection "upgrade"
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_upgrade;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 实时性优化
proxy_buffering off;
proxy_request_buffering off;
proxy_socket_keepalive on;
tcp_nodelay on;
# 延长超时
proxy_connect_timeout 60s;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
}分别启动容器
docker-compose up -d- 注意:为了确保 derper 能够正确验证客户端,
derper应该在tailscaled启动之后再运行
登录服务端管理平台:https://hs-ui.jgaga.tk:12335
终端下生成 99 年 API Key (UI 用):
docker exec headscale-server headscale apikeys create --expiration 99y得到密钥后,先在平台上注册个通用帐号(也就是新用户)。

上图绿色的地点一下,填写一个用户名,即后面注册的节点都在这个用户下。

注册本地客户端linux下(10.20.20.8)本地tailscale节点 这是纯路由的方式 ,不指定出口网关,只适合回家,走家里的网络(推荐这种)手机省电。
docker exec tailscale-node tailscale up --login-server https://hs.jgaga.xyz:12335 --advertise-routes=fd88::/64,10.20.20.0/24,f2b0::/18,28.0.0.0/8,100.100.200.0/24,91.108.56.0/22,91.108.4.0/22,91.108.8.0/22,91.108.16.0/22,91.108.12.0/22,149.154.160.0/20,91.105.192.0/23,91.108.20.0/22,185.76.151.0/24,2001:67c:4e8::/48,2001:b28:f23f::/48,2a0a:f280::/32,2001:b28:f23c::/48,2001:b28:f23d::/48 --accept-dns=false -reset上面的ip段中,你需要修改4个ip段,即你的内网ipv4,ipv6的段,还有fakeip的v4和v6的ip段,如上面我的四个段为:
fd88::/64,10.20.20.0/24,f2b0::/18,28.0.0.0/8,
运行完上面的命令,会得到一个链接,后面就是密钥,得到密钥后再登录管理平台:
https://hs-ui.jgaga.xyz:12335
添加客户端,完成注册~

下图是添加客户端,会提示输入密钥:

服务端安装配置完了~
客户端不管是linux下,还是手机端,还是windows端,向注册流程都是一样的,找到客户端的注册入口,输入服务端的网址如:https://hs.jgaga.tk:12335/通用帐号
手机客户端手可下载tailcals客户端软件。
添加新设备:手机端选 Log in to another server -> 填入你的域名及刚才在服务平台注册的通用帐号,确认后会出现密钥。 注册节点,手机端获取密钥后,在服务端添加密钥,完成手机端在服务端的注册 现在手机打开客户端,直接即可路由走隧道了~
其它说明:
- 查看客户端节点命令
docker exec tailscale status- 查看当前使用 / 可用的 DERP
docker exec tailscale-node tailscale netcheck上面完成了,就可以正常使用了,下面的只是做为一些知识扩展
下面是linux下的tailscale客户端做出口网关的命令组合:
linux客户端注册服务端开启各种功能,各命令参数配合使用会有不同的功能。
例如:
如果你确实想重新触发登录流程: 如果你想彻底退出当前账号并重新获取登录链接,可以执行命令:
docker exec tailscale-node tailscale up \
--login-server https://hs.jgaga.xyz:12335 \
--advertise-routes=10.20.20.0/24 \
--advertise-exit-node \
--accept-routes \
--accept-dns=false \
--force-reauth \
--reset–accept-dns=true \ #接受服务端指定的dns
–advertise-exit-node \ # 指定为出口网关
–advertise-routes=10.20.20.0/24 \ #允许其它节点访问内网段
去掉–accept-routes \ #这是让这台设备不让其它设备看到自己的内网– advertise-routes=10.20.20.0/24 \
如果是第三方出口网关linux节点:
第三方节点设备可以用tailscale官方的一键安装命令即可:
curl -fsSL https://tailscale.com/install.sh | sh既然你只想让它做出口网关(Exit Node)和子网路由器(Subnet Router), 请使用以下去掉了 –accept-routes 的命令:
docker exec tailscale-node tailscale up \
--login-server=https://hs.jgaga.xyz:12335 \
--accept-dns=false \
--advertise-exit-node \
--reset两个更“干净”的推荐模板
🔹 纯出口节点
docker exec tailscale-node tailscale up \
--reset \
--login-server=https://hs.jgaga.xyz:12335 \
--advertise-exit-node \
--accept-dns=true🔹 纯子网路由器(不出网)
docker exec tailscale-node tailscale up \
--reset \
--login-server=https://hs.jgaga.xyz:12335 \
--advertise-routes=10.20.20.0/24 \
--accept-dns=true🔹 命令不同,千变万化