利用 Caddy 反代 Tailscale DERP 服务

需求背景

因为运营商对于 IPv4 及 公网IP 的政策收窄,现在的普通家宽所获得的 IP 都是 NAT 过后的私有 IP。人在外已经无法通过 DDNS 等方式直连到家里的 NAS 等服务了。为了能够从外部访问到家里的 NAS 等设备,需要通过 Tailscale 的 VPN 服务来实现外部访问。

前期准备

网络质量可以参考下方

网络质量

整体的性能、网络测试可以参考 NodeQuality

Docker 环境安装

SHELL
apt install curl -y && apt install wget -y
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
点击展开查看更多

Docker Compose

完整 Docker Compose 文件如下:

YAML
networks:
  caddynet:
    external: true  # 如果你已有 caddynet 网络,保留 external

services:
  caddy:
    image: markd3ng/caddy_custom
    container_name: caddy
    restart: unless-stopped
    ports:
      # - "80:80"     # HTTP 验证用
      - "443:443"   # TLS/HTTPS 用
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./caddy_data:/data
      - ./caddy_config:/config
    environment:
      - CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN}  # 从 .env 文件中读取
    networks:
      - caddynet

  derper:
    image: fredliang/derper
    container_name: derper
    restart: always
    environment:
      - DERP_DOMAIN=example.com
      # - DERP_CERT_MODE=manual  # derper 自签,改用 Caddy 反代
      - DERP_ADDR=:8080             # 监听内部端口
      - DERP_STUN=true
      # - DERP_VERIFY_CLIENTS=true
      - DERP_STUN_PORT=3478
    # volumes:
      # - /var/run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sock
    ports:
      - "3478:3478/udp"             # 外部设备用 STUN 连接
    networks:
      - caddynet
点击展开查看更多

Docker Compose 解析

这个 docker-compose.yml 文件定义了两个服务:caddy 和 derper,它们都连接到一个名为 caddynet 的 外部 Docker 网络。

networks 配置
YAML
networks:
  caddynet:
    external: true
点击展开查看更多

PS:需要手动用 docker network create caddynet 创建 caddynet 的网络。

服务:Caddy
YAML
services:
  caddy:
    image: markd3ng/caddy_custom
    container_name: caddy
    restart: unless-stopped
    ports:
      - "443:443"   # 监听外部 HTTPS 流量(可选开启 80)
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./caddy_data:/data
      - ./caddy_config:/config
    environment:
      - CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN}
    networks:
      - caddynet
点击展开查看更多
项目 说明
image 使用自定义构建的 caddy 镜像(可能包含额外插件)。1见解释。
ports 仅开放 443(HTTPS),若要自动申请证书,建议开启 80(被注释掉)。
volumes 绑定本地配置:
- Caddyfile 是主配置文件
- caddy_data 存储证书等数据
- caddy_config 存储配置状态
environment 使用环境变量 CLOUDFLARE_API_TOKEN,供 Caddy 使用 DNS 方式申请证书(Cloudflare 提供 DNS 验证)。
networks 使用共享网络 caddynet,以便跟其他服务(如 derper)互通。
服务:Derper
YAML
  derper:
    image: fredliang/derper
    container_name: derper
    restart: always
    environment:
      - DERP_DOMAIN=example.com
      - DERP_ADDR=:8080
      - DERP_STUN=true
      - DERP_STUN_PORT=3478
    ports:
      - "3478:3478/udp"
    networks:
      - caddynet
点击展开查看更多
项目 说明
image 使用 fredliang/derper 镜像,运行 Tailscale 的中继服务器。 如果需要启用 Client verification,见2
DERP_DOMAIN 设置公开使用的域名(需与 Caddy 配置反向代理对应)。
DERP_ADDR=:8080 监听容器内部的 8080 端口(不直接对外开放,走反向代理)。
DERP_STUN=true 启用 STUN(用于打洞)
DERP_STUN_PORT=3478 STUN 使用的端口,映射到主机 3478/udp
ports 仅暴露 3478/udp,用于 STUN 功能,8080 没有暴露(仅被 Caddy 内部反代)。
networks caddy 共享 caddynet 网络,Caddy 可以通过容器名访问 derper 服务。
访问流程
  graph TD
    subgraph "Internet"
        A[Client]
        D[Cloudflare API]
        E[Let's Encrypt]
    end

    subgraph "Your Server / Docker"
        subgraph "caddynet Network"
            direction LR
            B(Caddy - Reverse Proxy)
            C(Derper - DERP Server)
            B -- Reverse Proxy to :8080 --> C
        end
    end

    A -- DERP over HTTPS (derper.saru.im:443) --> B
    A -- STUN Request (UDP:3478) --> C
    C -- STUN Response --> A

    B -- Step 1: Request TLS Certificate --> E
    E -- Step 2: Issue DNS-01 Challenge --> B
    B -- Step 3: Update TXT Record --> D
    D -- Step 4: Confirm TXT Record --> E
    E -- Step 5: Issue TLS Certificate --> B

Caddyfile

YAML
derper.example.com {
  reverse_proxy derper:8080
  tls {
    dns cloudflare {env.CLOUDFLARE_API_TOKEN}
  }
}
点击展开查看更多

解析

  1. derper.example.com 这是一个 站点块(site block),表示这个配置应用于请求域名为 derper.saru.im 的 HTTPS 请求。
  1. reverse_proxy derper:8080 反向代理指令。
TEXT
[Client] ---> Caddy (443) ---> http://derper:8080 (容器间通信)
点击展开查看更多
  1. tls { … } 这一块配置 HTTPS/TLS 的证书获取方式。
YAML
tls {
  dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}
点击展开查看更多

PS:

  1. 确保 Cloudflare 上有一个 A 记录 derper.saru.im 指向你 Caddy 所在服务器的公网 IP;
  2. 环境变量 CLOUDFLARE_API_TOKEN 应至少包含 Cloudflare 的 Zone.DNS 权限。API 的申请请自行查阅教程;
  3. 如果你用了 .env 文件,确保 docker-compose.yml 和 .env 在同一目录下;

.env

BASH
CLOUDFLARE_API_TOKEN=type_your_own_api_here
点击展开查看更多

整体目录结构如下

BASH
derper-caddy/
├── Caddyfile               # Caddy 的配置文件
├── docker-compose.yml      # Docker Compose 编排
├── .env                    # 存储 Cloudflare API Token
├── caddy_data/             # Caddy 自动生成的证书等数据(映射目录)
├── caddy_config/           # Caddy 的配置状态数据
点击展开查看更多
  1. 安装 Tailscale 的 Linux 客户端,教程自行查找;
  2. Docker Compose 文件中的 # DERP_VERIFY_CLIENTS=true 去掉 # 启用。
YAML
FROM caddy:builder AS builder

RUN xcaddy build \
    --with github.com/caddy-dns/cloudflare \
    --with github.com/caddyserver/forwardproxy

FROM caddy:latest

COPY --from=builder /usr/bin/caddy /usr/bin/caddy
点击展开查看更多

运行

Caddyfile,Docker Compose File,.ENV 都准备好后在终端执行。

BASH
docker compose up -d
点击展开查看更多

验证

浏览器打开 https://derper.example.com 显示 DERP 字样证明成功。

Tailscale 组网

这部分可以参考 多多先生 的文章。


  1. Caddy 的容器镜像是编译了第三方的插件,如果有需要编译自己的 Caddy,可以自己去编译。可以参考 3 https://github.com/caddy-dns/cloudflare https://github.com/caddyserver/forwardproxy ↩︎

  2. Client verification ↩︎

  3. Dockerfile ↩︎

版权声明

作者: Mark_Deng

链接: https://www.saru.im/posts/0000/

许可证: CC BY-NC-SA 4.0

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. Please attribute the source, use non-commercially, and maintain the same license.

评论

开始搜索

输入关键词搜索文章内容

↑↓
ESC
⌘K 快捷键