Docker版headscale&derp&tailscale配合npm反向代理回家的方法

说明 这教程全网应该是唯一的 内网部署。

因为太实用了,不管是回家,还是建立私有隧道,或者是添加节点等等吧都行,不过也有硬性要求,就是家里要使用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申请证书,下面有教程。

DomainForward IPForward Port
https://hs.jgaga.xyz:1233510.20.20.88080 (Headscale 容器对外服务端口)
https://derps.jgaga.xyz:1233510.20.20.88082 (DERP 容器对外服务端口)
https://hs-ui.jgaga.xyz:1233510.20.20.89443 (web页后台管理端口)
http://10.20.20.810.20.20.881 (Nginx Proxy Manage后台管理端口)

开始安装

docker一键安装

curl -fsSL https://raw.githubusercontent.com/hanigege/scripts/main/docker.sh -o docker.sh && sh docker.sh

docker.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   NAMES

1. 宿主机底层网络与内核准备 (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 -p

2. 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.yaml
services:
  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=7829

3. derper中继容器安装

  • 建立目录
mkdir ~/data/docker_data/derps
cd ~/data/docker_data/derps
  • 建立docker-compose.yaml
nano docker-compose.yaml
services:
  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=80
  • VERIFY_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.yaml
services:
  # 服务 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.yamlderp.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.sqlite

4. Nginx Proxy Manager容器安装

  • 建立目录
mkdir ~/data/docker_data/npm
cd ~/data/docker_data/npm
  • 建立docker-compose.yaml
nano docker-compose.yaml
services:
  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

CleanShot 2026-03-02 at 11.47.51@2x

npm上的域名反向配置,可以绑定docker接口的ip或者是内网ip:10.20.20.8

Domain内网 IP端口
hs.jgaga.xyz172.17.0.18080
derps.jgaga.xyz172.17.0.18082
hs-ui.jgaga.xyz172.17.0.19443

下面的配置选项都照下图开启就可以了

CleanShot 2026-03-02 at 12.58.56@2x

Websockets Support 必须开启 长连接支持

CleanShot 2026-03-02 at 12.50.09@2x

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 伪装规则已正常配置,保障内网设备回环与正常出海)

导入后,如下图,关注导入规则的位置,都放最上面就行了!

ros

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

CleanShot 2026-03-02 at 14.08.59@2x

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

得到密钥后,先在平台上注册个通用帐号(也就是新用户)。

CleanShot 2026-03-02 at 13.53.10@2x

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

CleanShot 2026-03-02 at 13.53.30@2x

注册本地客户端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 添加客户端,完成注册~

CleanShot 2026-03-02 at 13.54.04@2x

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

CleanShot 2026-03-02 at 13.57.24@2x

服务端安装配置完了~

客户端不管是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

🔹 命令不同,千变万化

完了,没啥了,不懂的就再问ai吧~