前言
最近在阿里云服务器上部署一个基于 Laravel 12 + Filament v4 的项目(代号“土豆食堂”),使用 Docker Compose 容器化部署,后端 Nginx + PHP-FPM,前端通过 Nginx Proxy Manager (NPM) 实现 HTTPS 反向代理。部署完成后,一切看似正常,但访问应用时发现前端资源(如 Filament 的 app.js
)的 URL 被错误生成成 https://lynx.wkarrow.top:80/js/filament/filament/app.js?v=4.1.6.0
,多出的 :80
端口导致浏览器无法加载资源,报 404 错误。
这不是代码 bug,而是典型的 HTTPS 代理到 HTTP 后端的头部传递问题。NPM 默认将 X-Forwarded-Port
设置为后端端口 80,而 Laravel 在生成资产 URL 时会根据此头部附加端口,导致与 HTTPS scheme 不匹配。
本文记录了问题排查与最终解决方案,重点分享后端 Nginx 的备选修复方法(适用于 NPM 配置无效的情况)。希望对类似部署遇到坑的开发者有帮助!
问题复现与分析
环境概述
- 项目:Laravel 12 + Filament v4,使用 Vite 构建前端资源。
- 部署:Docker Compose(
docker-compose.internet.yml
),包含 app (PHP-FPM)、nginx、mysql、redis 等服务。 - 代理:NPM 监听 443 端口,强制 HTTPS,转发到后端 Nginx 的 80 端口。
- Nginx 配置:
default.conf
中已传递X-Forwarded-*
头部到 PHP-FPM。
故障现象
- 正常预期:
https://lynx.wkarrow.top/js/filament/filament/app.js?v=4.1.6.0
- 实际生成:
https://lynx.wkarrow.top:80/js/filament/filament/app.js?v=4.1.6.0
- 影响:浏览器 Network 面板显示资源 404,应用界面 JS/CSS 加载失败,Filament 面板无法交互。
根因分析
- NPM 作为前端代理,添加
X-Forwarded-Proto: https
和X-Forwarded-Port: 80
(后端端口)。 - 后端 Nginx 将这些头部传递给 PHP-FPM(
fastcgi_param HTTP_X_FORWARDED_PORT $http_x_forwarded_port;
)。 - Laravel 的
Request
对象信任代理头部(config/trustedproxies.php
),读取X-Forwarded-Port=80
。 - URL 生成器(如
asset()
或 Filament 的资源 URL)检测到端口非 HTTPS 默认 443,故附加:80
。 - 参考 Laravel 12 文档(HTTP Requests 部分):
request()->getPort()
优先从X-Forwarded-Port
取值,导致畸形 URL。
这是一个常见痛点,尤其在 Docker + 反向代理环境中。
解决方案
我先尝试了在 NPM 的 Advanced 选项卡添加 proxy_set_header X-Forwarded-Port 443;
,但由于 NPM 的自定义配置有时不稳定(可能与版本或缓存相关),最终选择了后端 Nginx 的备选修复——直接在 default.conf
中覆盖端口逻辑。这方法更可靠,且不依赖代理工具。
步骤:修改后端 Nginx 配置
编辑配置文件:在项目目录下,打开
./docker-internet/nginx/default.conf
(或挂载路径/etc/nginx/conf.d/default.conf
)。定位 PHP 处理块:找到
location ~ \.php$
部分(已存在 FastCGI 配置)。添加覆盖逻辑:在
fastcgi_param
相关行后,插入以下 Nginx 指令:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass app:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
# 原有头部传递
fastcgi_param HTTP_X_FORWARDED_FOR $http_x_forwarded_for;
fastcgi_param HTTP_X_FORWARDED_PROTO $http_x_forwarded_proto;
fastcgi_param HTTP_X_FORWARDED_HOST $http_x_forwarded_host;
fastcgi_param HTTP_X_FORWARDED_PORT $http_x_forwarded_port;
# 新增:基于 proto 覆盖端口,确保 HTTPS 时为 443
set $real_port $http_x_forwarded_port;
if ($http_x_forwarded_proto = "https") {
set $real_port "443";
}
fastcgi_param HTTP_X_FORWARDED_PORT $real_port;
}- 解释:
set $real_port $http_x_forwarded_port;
:默认使用传入端口。if ($http_x_forwarded_proto = "https") { set $real_port "443"; }
:如果 scheme 是 HTTPS,则强制覆盖为 443(忽略后端 80)。- 最后用
$real_port
替换原参数,确保 Laravel 读取正确端口。
- 解释:
保存并重启服务:
1
sudo docker compose -f docker-compose.internet.yml restart nginx
- Nginx 会优雅重载,无需重建整个容器。
辅助优化(可选,但推荐):
- 确认
.env
中的APP_URL=https://lynx.wkarrow.top
(无端口)。 - 清除 Laravel 缓存:
1
2
3sudo docker compose -f docker-compose.internet.yml exec app php artisan config:clear
sudo docker compose -f docker-compose.internet.yml exec app php artisan cache:clear
sudo docker compose -f docker-compose.internet.yml exec app php artisan optimize
- 确认
验证与测试
- 浏览器测试:清空缓存,访问
https://lynx.wkarrow.top
,打开 F12 > Network,检查资源 URL(如app.js
)是否无:80
,并确认 200 OK。 - 命令行验证:预期返回 200,无重定向或 404。
1
curl -I https://lynx.wkarrow.top/js/filament/filament/app.js
- 日志检查:若仍有问题,查看容器日志:
1
sudo docker compose -f docker-compose.internet.yml logs nginx app | grep -i "forwarded\|port"
修复后,Filament 面板加载顺畅,前端交互完美!
总结与建议
这个坑让我意识到,在容器化部署中,代理头部的处理至关重要。优先尝试 NPM 的 Advanced 配置,如果无效,后端 Nginx 的 if
逻辑是稳妥备选。未来更新项目时,记得 git pull 后检查此配置是否覆盖。
如果您也遇到类似问题,欢迎评论区交流!项目源码在 GitHub 开源中,星标支持下~
参考:
- Laravel 12 文档:HTTP Requests
- Nginx Proxy Manager 官方指南:Advanced Tab
本文基于实际部署经验撰写,如有疑问,欢迎讨论。