记一次 Nginx 代理导致 API 500 错误的排查与修复
问题描述
前端访问http://localhost:9527/dev-api/Login/getStaticResource返回 500 Internal Server Error,直接访问http://anyu-portal.test/正常。
技术栈
- 前端:Vue.js + Vue CLI (devServer proxy)
- 后端:ThinkPHP 5.x + PHP 7.4
- 容器:Docker Compose (nginx, php-fpm)
- 代理:Nginx 反向代理
请求链路分析
浏览器 localhost:9527/dev-api/Login/getStaticResource ↓ Vue devServer proxy (vue.config.js) Nginx anyu-portal-frontend.test/api/Login/getStaticResource ↓ Nginx location /api/ proxy_pass (anyu-front.conf) Nginx anyu-portal.test/Login/getStaticResource ↓ Nginx location ~ .php$ fastcgi_pass (anyu-portal.conf) PHP-FPM anyu-portal/Public/index.php配置文件检查
1. Vue devServer 代理配置
vue.config.js:
proxy:{[process.env.VUE_APP_BASE_API]:{target:'http://anyu-portal-frontend.test/api',changeOrigin:true,pathRewrite:{['^'+process.env.VUE_APP_BASE_API]:''}}}VUE_APP_BASE_API=/dev-api,所以:
- 请求
/dev-api/Login/getStaticResource - 被转发到
http://anyu-portal-frontend.test/api/Login/getStaticResource
2. Nginx 前端站点配置
anyu-front.conf:
server { listen 80; server_name anyu-frontend.test *.anyu-frontend.test; root "/www/anyu-portal-frontend"; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_set_header Host $http_x_forwarded_host; proxy_pass http://anyu-portal.test/; } }3. Nginx 后端站点配置
anyu-portal.conf:
server { listen 80; server_name anyu-portal.test; root /www/anyu-portal/Public; if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s=$1 last; } location ~ \.php$ { fastcgi_pass php:9000; include fastcgi-php.conf; include fastcgi_params; } }4. Docker Compose 网络配置
docker-compose.yml:
services:nginx:extra_hosts:-"openapi:127.0.0.1"-"lvs:127.0.0.1"-"admin:127.0.0.1"-"portal:127.0.0.1"# 缺少: anyu-portal.test:127.0.0.1排查过程
步骤 1:直接测试后端 API
从 nginx 容器内部直接访问后端:
dockerexecnginxcurl-vhttp://anyu-portal.test/Login/getStaticResource结果:返回 200 OK,后端 API 本身正常。
步骤 2:测试代理链路
从 nginx 容器内部通过前端域名访问:
dockerexecnginxcurl-vhttp://anyu-portal-frontend.test/api/Login/getStaticResource结果:返回 500 Internal Server Error。
步骤 3:检查 Host 头问题
直接访问时 curl 会发送Host: anyu-portal.test,但通过/api/代理时:
proxy_set_header Host $http_x_forwarded_host;$http_x_forwarded_host是客户端请求的X-Forwarded-Host头,通常为空。当 Host 头为空时,后端 ThinkPHP 的getStaticResource方法无法正确识别SERVER_NAME,导致找不到合作伙伴配置。
步骤 4:检查域名解析
nginx 容器的extra_hosts中没有配置anyu-portal.test,虽然之前已经添加了,但需要确认是否生效。
根因分析
问题有两个层面:
Host 头为空:
proxy_set_header Host $http_x_forwarded_host;设置了一个通常为空的变量,导致转发到后端时没有正确的 Host 头。域名解析:nginx 容器内部无法解析
anyu-portal.test(虽然之前已添加到extra_hosts)。
后端 ThinkPHP 的getStaticResource方法依赖SERVER_NAME获取合作伙伴配置:
publicfunctiongetStaticResource(){$server_name=SERVER_NAME;// 依赖 $_SERVER['SERVER_NAME']$redis=newRedis();$redis->connect(C("REDIS_HOST"),C("REDIS_PORT"));// ...if(!C('PARTNER')){$this->setPartner($redis,$server_name);// 根据 server_name 获取合作伙伴}// ...}当 Host 头为空或不正确时,SERVER_NAME无法正确识别,导致C('PARTNER')为空,后续操作失败。
修复方案
修复 1:修改 Nginx 代理配置
文件:services/nginx/conf.d/anyu-front.conf
# 修改前 location /api/ { proxy_set_header Host $http_x_forwarded_host; proxy_pass http://anyu-portal.test/; } # 修改后 location /api/ { proxy_set_header Host anyu-portal.test; proxy_pass http://anyu-portal.test/; }修复 2:添加域名解析
文件:docker-compose.yml
services:nginx:extra_hosts:-"openapi:127.0.0.1"-"lvs:127.0.0.1"-"admin:127.0.0.1"-"portal:127.0.0.1"-"anyu-portal.test:127.0.0.1"# 新增修复 3:重载 Nginx 配置
dockerexecnginx nginx-sreload验证
dockerexecnginxcurl-vhttp://anyu-portal-frontend.test/api/Login/getStaticResource结果:返回 200 OK,JSON 数据正常。
总结
经验教训
Host 头很重要:反向代理时,
proxy_set_header Host必须设置正确的值,后端框架(如 ThinkPHP)依赖它来识别当前站点。容器内部域名解析:Docker Compose 的
extra_hosts只对容器内部生效,宿主机的 hosts 文件不会自动同步到容器。排查顺序:先测试直接访问后端,再测试完整代理链路,定位问题出在哪一层。
变量陷阱:
$http_x_forwarded_host是客户端请求头,不是 Nginx 内置变量,不要误以为它会自动填充。
最佳实践
location /api/ { proxy_set_header Host $proxy_host; # 使用 proxy_pass 中指定的主机名 proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://backend/; }使用$proxy_host可以自动获取proxy_pass中指定的主机名,比硬编码更灵活。