用家庭服务器构建公网服务

2025 年 11 月 15 日

前几天给家里配好了 DDNS,打开了防火墙,这样如果外部有 IPv6 网络的话,就能简单的连接家里的机器了。我对外放开了自己的一个书签页,还有一个电视服务。同城访问的话,速度还不错。

但只做好 DDNS 的话,明显还是不够的。

仅用 DDNS 时存在的问题

看问题本质,DDNS 只是给 IP 配了一个或多个域名。对于家庭服务器,一台机器就一个公网 IPv6 地址(多了没意义),但一台机器可能跑一堆服务,因此需要维护域名和实际服务的映射关系。这点光靠 DNS 记录是做不到的。

家里的服务通常都会以 http 方式对外提供,在外面访问时,https 基本是必备。那么就需要把 http 转成 https。

另外,公网 IPv6 地址必须在支持 IPv6 的网络中才能正常访问。现在移动网络基本都支持 IPv6 了,但在外面连 Wifi 时,通常还是 IPv4-Only 的,这样无法访问公网 IPv6 地址的。

下面描述的方法,仅适用于我自己的用例,能达成以下几点:

  1. 通过不同的二级/三级域名,访问不同的服务。

  2. 获得 https 访问地址。

  3. IPv4-Only 也可以访问,但有代价。

先简单说结论,对于1/2,加一个应用网关服务;对于 3,用 Cloudflare CDN。

通过应用网关服务管理域名和实际服务的映射

应用网关服务可选项有 nginx/traefik/caddy,另外国产的 lucky 似乎也能完成同样功能。优劣大家自己问 AI 即可。

我一开始我还尝试了 pangolin,但发现免费版本似乎不够用,遂放弃。最后选的 traefik,因为开源且原生支持 tcp 映射。

基础概念上,traefik 要接管 80/443 以及其他想要暴露出去的端口,然后配置域名对应后面的 http 服务即可。

为了减少重复工作,traefik 用 docker-compose 跑,配置包括 install configuration(静态配置)和 routing configuration(动态配置) ,我自己的最简配置方法如下:

# docker-compose.yml
services:
  traefik:
    image: "traefik:latest"
    container_name: "traefik"
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "9000:8080"
    volumes:
      - "./config:/etc/traefik"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
    networks:
      - ipv6_network

networks:
  ipv6_network:
    enable_ipv6: true

然后在 docker-compose.yml 所在目录下建立 config 目录,创建以下几个文件:

  • config/traefik.yml
  • config/acme.json,空文件,但要注意将权限改为 600。
  • config/dynamic/websites.yml

参考内容见下:

# config/traefik.yml,静态配置

log:
  level: INFO

api:
  dashboard: true       # 开启 Dashboard
  insecure: true         # 允许 http://访问(测试用)

entryPoints:
  web:
    address: ":80"
    http:
      redirections:		# 默认 80 转 443
        entryPoint:
          to: websecure
          scheme: https
          permanent: true
  websecure:
    address: ":443"
  traefik:
    address: ":8080"     # Dashboard 所在端口

providers:
  file:
    directory: /etc/traefik/dynamic
    watch: true

certificatesResolvers:
  myresolver:
    acme:
      email: [email protected]
      storage: /etc/traefik/acme.json
      httpChallenge:
        entryPoint: web
# config/dynamic/websites.yml
http:
  routers:
    sub1:
      rule: "Host(`sub1.example.com`)"      # 匹配域名
      entryPoints:
        - websecure                    # 监听 443
      service: sub1_service
      tls:
        certResolver: myresolver       # 启用自动HTTPS(如果配置了)
    sub2:
      rule: "Host(`sub2.example.com`)"
      entryPoints:
        - websecure
      service: sub2_service
      tls:
        certResolver: myresolver

  services:
    sub1_service:
      loadBalancer:
        servers:
          - url: "http://127.0.0.1:8080"   # 实际服务地址
    sub2_service:
      loadBalancer:
        servers:
          - url: "http://192.168.0.1:8080"

这个例子里建立了两个映射:

  • https://sub1.example.com -> http://127.0.0.1:8080
  • https://sub2.example.com -> http://192.168.0.1:8080

当然,要想功能正常启用,先确保域名在 cloudflare 上已经正确映射到当前 IPv6 地址,并且防火墙已放行。

Cloudflare CDN 管理

本质上,通过域名访问服务时,最后要一定走到一个 IP,返回数据。那么很自然这个线路肯定越短越好。

既然用了 IPv6 地址,国内连国内的话,肯定是直达最快。但要想 IPv4-Only 网络也能访问,那只能给它一个公网 IPv4 地址。

那么问题就很明显了,运营商肯定是不给公网 IPv4 地址的,所以只能找 Cloudflare 帮忙。

有两个方案可以选择,优劣如下:

方案优点缺点
单域名多记录单域名好记,任意网络可用,隐藏真实 IPIPv6 也必须走 CDN,速度太慢
双域名走 IPv6 速度快需要根据所在网络是否支持 IPv6,区分两个域名使用

单域名多记录

简单说就是,在 Cloudflare 上,除了原有的 AAAA,再建立一个 A 记录,指向 192.0.2.1。然后把两条记录的橙色云朵都打开。

这样的后果是,所有的请求,无论是 v4 还是 v6,都会从 Cloudflare CDN 走一圈,所以慢了很多。

双域名

原来的 AAAA 保持灰色云朵不变。新建一个记录,CNAME 指向 AAAA 的记录名,橙色云朵打开。

这样如果走原来的域名,就是 IPv6 直连,速度最快。如果此路不通,那就用新的域名,通过 Cloudflare CDN 访问,速度虽然慢,但保证基本能用。

其他注意事项

如果是橙色云朵打开,也就是走 Cloudflare CDN 时,对于二级域名,Cloudflare 会自动处理证书问题;但如果是三级域名,就没有证书了。

灰色云不用考虑这个问题,证书必须自行处理,这点交给 traefik 就好了。

Top