公众号旧文

Cloudflare Tunnel + Nginx 多站点排障记录

作者:胖子阅读 —

我有一台位于洛杉矶的 VPS:

我买了一台 10元/年 的服务器(新手友好)

CPU:1 Core
内存:512MB
硬盘:1.2GB
系统:Debian 13

原本部署了两个网站:

lax-mini.19900429.xyz
cover.19900429.xyz

对应关系如下:

Cloudflare Tunnel
├── lax-mini.19900429.xyz -> localhost:80
└── cover.19900429.xyz    -> localhost:8088

Nginx:

80    -> lax-mini
8088  -> YouTube Cover

虽然能用,但这种一站一端口的方式不够规范。

于是决定改造成:

Nginx
└── 80
    ├── lax-mini.19900429.xyz
    └── cover.19900429.xyz

通过 server_name 区分站点。

第一步:修改 Nginx

默认站点:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name lax-mini.19900429.xyz;
    root /var/www/html;
}

YouTube Cover:

server {
    listen 80;
    server_name cover.19900429.xyz;
    root /var/www/youtube-cover;
}

重载:

nginx -t
systemctl reload nginx

本地验证:

curl -H "Host: lax-mini.19900429.xyz" http://127.0.0.1

返回:

<title>lax-mini</title>

再测试:

curl -H "Host: cover.19900429.xyz" http://127.0.0.1

返回:

<title>YouTube封面提取工具</title>

说明 Nginx 配置正确。

第二步:修改 Cloudflare Tunnel

原配置:

ingress:
  - hostname: lax-mini.19900429.xyz
    service: http://localhost:80
  - hostname: cover.19900429.xyz
    service: http://localhost:8088

改成:

ingress:
  - hostname: lax-mini.19900429.xyz
    service: http://localhost:80
  - hostname: cover.19900429.xyz
    service: http://localhost:80

重启:

systemctl restart cloudflared

第三步:发现问题

此时本地测试正常:

curl -H "Host: cover.19900429.xyz" http://127.0.0.1

返回:

<title>YouTube封面提取工具</title>

但外网访问:

curl https://cover.19900429.xyz

返回:

<title>lax-mini</title>

两个域名都显示同一个页面。

第四步:排查 Tunnel 配置

发现系统里居然存在两份配置:

/etc/cloudflared/config.yml
/root/.cloudflared/config.yml

查看:

cloudflared tunnel ingress rule https://cover.19900429.xyz

发现读取的是:

/root/.cloudflared/config.yml

而里面仍然保留:

cover -> localhost:8088

一度以为问题出在这里。

后来检查 systemd:

systemctl cat cloudflared

发现:

ExecStart=/usr/bin/cloudflared \
  --config /etc/cloudflared/config.yml \
  tunnel run

说明服务实际使用的是:

/etc/cloudflared/config.yml

这条线索最终被排除。

第五步:查看 Nginx 实际收到的 Host

修改日志格式:

log_format hostlog '$remote_addr "$host" "$http_host" "$request"';

访问网站后看到:

::1 "cover.19900429.xyz" "cover.19900429.xyz" "GET / HTTP/1.1"

说明:

Cloudflare Tunnel
        ↓
Nginx

Host Header 完整保留,Tunnel 没有问题。

第六步:真正的原因

查看两个站点配置,默认站点:

listen 80 default_server;
listen [::]:80 default_server;

Cover 站点:

listen 80;

缺少:

listen [::]:80;

而访问日志显示:

::1

说明 cloudflared 使用 IPv6 localhost 连接 Nginx。

于是出现:

IPv4
cover -> youtube-cover
IPv6
cover -> default_server

最终所有请求都落到了默认站点。

修复

改为:

server {
    listen 80;
    listen [::]:80;
    server_name cover.19900429.xyz;
    root /var/www/youtube-cover;
}

重载:

nginx -t
systemctl reload nginx

再次访问:

https://cover.19900429.xyz

恢复正常。

最终架构

Internet
    │
    ▼
Cloudflare
    │
    ▼
Cloudflare Tunnel
    │
    ▼
localhost:80
    │
    ▼
Nginx
├── lax-mini.19900429.xyz
│   └── /var/www/html
│
└── cover.19900429.xyz
    └── /var/www/youtube-cover

所有网站统一监听 80 端口,通过 server_name 区分。

以后新增站点,只需要新增一个 Nginx Server Block 即可,无需再占用额外端口。

经验总结

  1. 一个服务器多个网站,优先使用 server_name,不要一站一端口。
  2. Cloudflare Tunnel 调试时要确认实际使用的是哪份配置文件。
  3. 默认读取的是用户目录配置,不一定是 systemd 服务使用的配置。
  4. 查看 Host Header 是排查反向代理问题最有效的方法之一。
  5. Nginx 同时部署 IPv4 和 IPv6 时,要成对出现,否则可能出现问题。