昨天下午,一个同事跑过来,脸色不太好看。他说他的 Laravel 项目在本地跑得好好的,一上传到服务器,访问任何路由都直接甩出 403 Forbidden。我第一反应是权限问题,让他去检查 storage 目录。他检查了,权限 777,没问题。然后他又检查了 nginx 配置,也没看出毛病。
最后我让他把 nginx 的 error log 拉出来看一眼。日志里写着:directory index of "/var/www/html/" is forbidden。问题就出在这——nginx 找不到 index 文件。
很多人遇到 403 就习惯性 chmod -R 777,这其实是在瞎忙活。403 的本质是 nginx 拒绝了请求,而拒绝的原因五花八门。最常见的一种,就是你的站点根目录下根本没有 nginx 能识别的入口文件。
拿 PHP 项目来说,nginx 默认配置里通常写着 index index.php index.html。如果你的项目入口是 public/index.php,而你在 server 块里把 root 指向了项目根目录,那 nginx 去根目录找 index.php,找不到,自然就 403 了。解决办法很简单:把 root 指向 public 目录,或者用 try_files 重写。
还有一种情况,纯粹是配置文件里少了个斜杠。比如 root /var/www/html 和 root /var/www/html/,在某些 nginx 版本里会导致路径拼接错误。你访问 /index.php,nginx 实际去找的是 /var/www/html/index.php,但如果你 root 写成了 /var/www/html 且后面没加 location 块,它可能会把 URI 直接拼上去,结果路径变成 /var/www/htmlindex.php——这肯定找不到文件,然后给你一个 403。
另一个容易踩的坑是 SELinux。我见过太多人把权限调到 777 还是 403,最后发现是 SELinux 的 httpd_sys_content_t 标签没打对。你可以用 ls -Z 看看文件的安全上下文,如果发现是 unconfined_u:object_r:user_home_t 这种,那就需要用 chcon -t httpd_sys_content_t 或者干脆 setenforce 0 先测试是不是这个原因。不过我不建议长期关 SELinux,那等于给自己埋雷。
还有一类 403 是 nginx 配置里写了 deny all。比如你为了防爬虫,在某个 location 里加了 deny all;,结果忘记给 PHP 文件的 location 放行了。或者是 allow 和 deny 的顺序写反了——nginx 的规则是按顺序匹配的,先匹配到 deny all 就直接拒绝了,后面的 allow 根本不会执行。
排查 403 有个笨办法,但很管用:直接在浏览器里访问一个不存在的文件,比如 http://yourdomain.com/test_nonexist.php。如果返回的是 404,说明 nginx 的路径解析没问题,问题出在入口文件或 location 规则上。如果还是 403,那多半是目录层面就被拦了。
最后说一个很多人忽略的点:fastcgi 配置。nginx 把 PHP 请求转发给 php-fpm 时,如果 fastcgi_pass 指向的 socket 或端口不对,php-fpm 那边会返回 403,但 nginx 日志里显示的往往是 connect() failed 或者 primary script unknown。这时候你得去检查 php-fpm 的 listen 配置和 nginx 的 fastcgi_pass 是否一致。我见过有人把 socket 路径写成了 /run/php/php7.4-fpm.sock,但实际 php-fpm 用的是 php8.1,路径里的版本号对不上,折腾了半天。
说到底,403 不是权限这一个维度的事。你得先看 error log,再逐层排查。别一上来就 chmod,那跟感冒了先吃抗生素一样,大概率没用。