一个看似简单的 Nginx 反向代理问题,背后是 server 块优先级、配置文件加载顺序的深度排查。

背景

前段时间,我把自己手工搭建的高可用集群(Keepalived+Nginx+Tomcat+MySQL)全部容器化,并用 Docker Compose 管理。在 Backup 主机(192.168.211.135)上,Nginx 容器正常运行,但在容器内 curl 可以正常访问 Tomcat 页面,而通过宿主机 IP 或外部浏览器访问时,却始终返回 Nginx 默认欢迎页。这是一个典型的“内外不一致”问题,我花了不少时间才找到根因。本文将完整记录排查与解决过程。


一、现象

在 Backup 主机上,执行:

docker exec -it nginx curl http://127.0.0.1

返回 Tomcat 页面(正确)。 但执行:

curl http://192.168.211.135

返回 Nginx 默认欢迎页(错误)。 Windows 浏览器访问同样结果。


二、排查思路

2.1 确认 80 端口监听方

ss -tlnp | grep :80

输出显示 nginx 进程在监听 80 端口,且进程路径为容器内的 nginx(不是宿主机 nginx),说明容器已正确接管端口。

2.2 检查容器内配置文件

docker exec -it nginx ls /etc/nginx/conf.d/

发现两个文件:default.conf 和 tomcat.conf。 查看 tomcat.conf 内容:

upstream web {
    server 192.168.211.128:8081;
    server 192.168.211.133:8081;
}
server {
    listen 80;
    server_name _;
    location / {
        proxy_pass http://web;
    }
}

配置看起来正确。

2.3 检查主配置文件

docker exec -it nginx cat /etc/nginx/nginx.conf

确认 http 块内包含 include /etc/nginx/conf.d/*.conf;,说明配置文件都会被加载。

2.4 查看所有监听 80 的 server 块

docker exec -it nginx sh -c "nginx -T 2>/dev/null | grep -A 10 'listen\s*80'"

输出显示两个 server 块:

  1. 第一个来自 default.conf,server_name localhost,根目录 /usr/share/nginx/html。

  2. 第二个来自 tomcat.conf,server_name _,反向代理。

关键点:当请求的 Host 头(例如 192.168.211.135)与任何 server_name 都不匹配时,Nginx 会选择第一个定义的 server 块来处理。由于 default.conf 先被加载,它成为了默认 server,因此返回欢迎页。

2.5 验证优先级

在容器内模拟外部请求:

docker exec -it nginx curl -v --header "Host: 192.168.211.135" http://127.0.0.1

返回 404(因为 default.conf 的根目录没有对应文件),证实了请求被第一个 server 块处理。


三、解决方案

要让 tomcat.conf 成为唯一的 80 端口 server 块,必须消除 default.conf 的干扰。 最直接的方法:覆盖 default.conf 为空文件。

3.1 创建空文件

touch /opt/nginx-backup/default.conf

3.2 修改 docker-compose.yml,挂载空文件到正确位置

services:
  nginx:
    image: my-nginx:1.1
    container_name: nginx
    restart: always
    network_mode: host
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /opt/nginx-backup/tomcat.conf:/etc/nginx/conf.d/tomcat.conf:ro
      - /opt/nginx-backup/nginx.conf:/etc/nginx/nginx.conf:ro
      - /opt/nginx-backup/default.conf:/etc/nginx/conf.d/default.conf:ro   # 覆盖

注意:必须挂载到 /etc/nginx/conf.d/default.conf,而非 /etc/nginx/default.conf,因为 Nginx 只加载 conf.d 目录下的 .conf 文件。

3.3 重新创建容器

cd /opt/nginx-backup
docker-compose down
docker-compose up -d

3.4 验证

curl http://192.168.211.135

此时返回 Tomcat 页面,问题解决。


四、原理总结

· Nginx 加载顺序:include 指令按文件名字母顺序加载配置文件,因此 default.conf 先于 tomcat.conf。 · 默认 server 块:当请求的 Host 头不匹配任何 server_name 时,Nginx 会使用第一个监听的 server 块作为默认。 · 解决方案:删除或覆盖默认的 server 块,让自定义配置成为第一个(或唯一)的 server 块。


五、踩坑与反思

  1. 配置文件位置:一开始我误将空文件挂载到 /etc/nginx/default.conf,导致无效。必须确认正确的加载路径。

  2. 检查所有监听:使用 nginx -T 查看完整配置,避免遗漏。

  3. 区分内部与外部访问:容器内 curl 127.0.0.1 成功不代表外部请求能正确路由,因为 Host 头不同。

  4. 优先采用挂载而非 docker cp:挂载方式便于调试和持久化,避免容器重启后配置丢失。


六、最终成果

现在,Backup 主机上的 Nginx 容器已能正常反向代理到 Tomcat 集群,访问 Backup IP 和 VIP 均返回正确页面。整个高可用容器化项目(MySQL、Tomcat、Nginx)已通过 Docker Compose 实现一键部署,后续可扩展监控、日志、CI/CD 等功能。


如果你也遇到类似问题,希望这篇文章能帮你少走弯路。欢迎留言交流!

更多推荐