记一次 Nginx 代理导致 API 500 错误的排查与修复
记一次 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 容器内部直接访问后端:
docker exec nginx curl -v http://anyu-portal.test/Login/getStaticResource
结果:返回 200 OK,后端 API 本身正常。
步骤 2:测试代理链路
从 nginx 容器内部通过前端域名访问:
docker exec nginx curl -v http://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 获取合作伙伴配置:
public function getStaticResource() {
$server_name = SERVER_NAME; // 依赖 $_SERVER['SERVER_NAME']
$redis = new Redis();
$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 配置
docker exec nginx nginx -s reload
验证
docker exec nginx curl -v http://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 中指定的主机名,比硬编码更灵活。
所有评论(0)