资讯详情

资讯详情

ThinkPHP动态页面404排查:宝塔8.5+Nginx环境实战修复

把ThinkPHP项目传到宝塔8.5的站点目录里打开首页没问题一点子链接直接404这种经历我猜不少人都碰到过。我第一次遇到的时候先怀疑是不是伪静态没开折腾了半小时后来发现根本不是那个事。这篇文章就以宝塔8.5 Nginx环境为背景专门聊聊动态PHP页面出现“找不到页面”时我一般怎么一步步定位和修复。内容会覆盖Nginx和PHP-FPM的协作逻辑、三类典型404的来源辨析、可以直接抄的配置修复方案以及宝塔里那些很容易被忽视的坑适合刚用宝塔部署PHP项目的人也适合已经被404折磨到想砸键盘的人。1. 为什么配置看着没问题动态页面还是会4041.1 先搞懂Nginx和PHP-FPM是怎么配合的很多人一上来就改伪静态、改location改了半小时还是404。其实问题根源往往在于没理解Nginx本身“不会执行PHP”。Nginx只是个静态文件服务器收到请求后如果发现访问的是.php文件或者location规则里命中的动态路径它会把这个请求通过FastCGI协议转发给后端的PHP-FPM进程。PHP-FPM解析完PHP脚本把生成的HTML返回给Nginx再由Nginx回给浏览器。整个链路是浏览器 - Nginx - PHP-FPM - 文件系统。这条链路上任何一个环节出了问题最终表现都可能是404。比如Nginx拿到一个请求本想让PHP-FPM处理但配置里没有匹配到PHP相关的location规则Nginx就会退而求其次把它当成一个普通文件请求去磁盘上找。磁盘上当然没有这个文件于是直接回404。这就是最典型的情况动态页面404但PHP-FPM压根没收到请求。明白了这个协作模型下一步才能谈排查。很多人问“为什么我用Apache就能跑换Nginx就404”答案很简单Apache通过mod_php把PHP解释器直接嵌进自身进程天然能执行PHPNginx没有这个能力它必须依赖外部PHP-FPM进程所有衔接全靠配置文件。所以Nginx下的404十有八九是衔接配置问题而不是程序本身问题。1.2 404到底是谁返回的三类常见来源要分清同样一个404页面来源可能完全不同。我习惯把Nginx环境下的404分成三类判断清楚是哪一类后面修起来就快很多。第一类是Nginx自己返回的404。响应头里通常能看到Server: nginx错误日志里会有一条类似open() /www/wwwroot/你的站点/index.php failed (2: No such file or directory)的记录。这种情况说明Nginx把请求当静态文件处理了而且没找到文件。原因可能是location没匹配到PHP、root路径配错、文件权限不对。第二类是PHP-FPM返回的404。请求已经成功转发给了PHP-FPM但PHP-FPM在解析脚本时发现“Primary script unknown”比如FastCGI sent in stderr: Primary script unknown while reading response header from upstream或者框架本身的路由匹配不上、抛出了404异常。这类情况表面上页面也是404但错不在Nginx配置而是PHP脚本路径解析或者框架路由的问题。第三类是伪静态重写导致的内部404。伪静态规则把URL重写到了某个内部路径但这个内部路径对应的PHP文件不存在或者重写规则和框架自带的路由规则互相冲突最终返回404。这类最隐蔽因为配置文件表面上都是对的伪静态也“生效”了但内部路径已经歪了。判断方法很直接先看Nginx错误日志再看PHP-FPM日志两条日志都没有异常就去访问重写后的URL和原始URL对比。下面我会把整个排查顺序完整梳理一遍。2. 正儿八经的排查顺序从日志到配置一步步来2.1 第一步日志会告诉你大部分真相我排查404从来不先看配置先看日志。宝塔面板默认情况下Nginx的访问日志和错误日志都在/www/wwwlogs/目录下按域名区分比如你的域名.log和你的域名.error.log。Nginx自身的全局错误日志在/www/server/nginx/logs/error.log。打开一个终端跑一下tail -f /www/wwwlogs/你的域名.error.log然后去浏览器刷新那个404页面。日志里如果新出现一行仔细看是No such file or directory、Permission denied还是Primary script unknown。这三种错误指向三种不同的修复方向No such file or directoryNginx在文件系统找不到目标文件。大概率是root配置和实际文件路径对不上或者请求被重写到了不存在的路径。Permission denied文件在但Nginx的worker进程或PHP-FPM的www用户没有读权限。宝塔默认运行用户是www你如果把文件手动上传且属主是root、权限又不够宽松很可能就是这个。Primary script unknown请求已经到PHP-FPM了但PHP-FPM根据SCRIPT_FILENAME参数解析不到脚本文件。多半是fastcgi_param参数配置错误或者location匹配和root拼接出了问题。看完Nginx日志再看PHP-FPM日志。宝塔里PHP-FPM的日志路径一般是/www/server/php/你的PHP版本/var/log/php-fpm.log。如果里面出现了和该URL相关的warning或error基本可以断定问题出在PHP侧。2.2 第二步确认PHP-FPM还活着且监听正常日志没炸不代表PHP-FPM就是好的还得确认进程状态和监听方式。命令行跑一下ps aux | grep php-fpm能看到php-fpm主进程和一堆worker进程就说明进程还活着。宝塔面板的软件商店里也能看到对应PHP版本的状态。这里要注意一个关键点PHP-FPM的监听方式有两种一种是TCP端口常见是127.0.0.1:9000另一种是Unix Socket比如unix:/tmp/php-cgi-74.sock。宝塔默认多半是Socket方式因为性能好一些。问题是Nginx站点配置里的fastcgi_pass必须和PHP-FPM实际监听的通道保持一致。如果PHP-FPM监听的是9000端口Nginx却把请求转发给/tmp/php-cgi-74.sock那就会报错。这种场景下表现通常是502但在某些特殊配置组合下也会表现为404所以排查时不能忽略这一步。怎么看PHP-FPM实际监听什么netstat -lnp | grep php-fpm能清晰看到监听的是TCP端口还是Socket文件。然后去站点配置文件里看fastcgi_pass写的是什么两边对上号排除这个隐患。2.3 第三步检查站点配置里的location和rewrite到这一步才开始看配置文件。宝塔8.5的站点配置文件在/www/server/panel/vhost/nginx/你的域名.conf伪静态规则文件在/www/server/panel/vhost/rewrite/你的域名.conf主Nginx配置在/www/server/nginx/conf/nginx.conf。打开站点配置文件重点看两个地方。第一个是location ~ \.php$这一段宝塔一般不会直接写fastcgi_pass那一串而是用include enable-php-74.conf;这样的方式引入。这个enable-php文件位于/www/server/nginx/conf/下里面写好了完整的PHP处理配置。如果站点选的PHP版本是7.4但include的是enable-php-73.conffastcgi_pass指向的Socket就可能是另一个版本PHP的需要去站点设置里重新选择PHP版本让宝塔自动修正。第二个重点就是伪静态规则。宝塔的伪静态功能其实就是维护rewrite/你的域名.conf这个文件然后主配置里通过include把它引进来。这里要注意Nginx配置里的location匹配是有顺序和优先级的location /里的rewrite规则和location ~ \.php$之间如果配合不好会出现“伪静态开了反而404”的怪现象。关掉伪静态、直接访问/index.php开头的URL如果通了说明就是重写规则的问题而不是PHP本身的问题。3. 几种典型场景的修复方案配置直接可以抄3.1 场景A最简单的PHP文件直接404日志显示No such file or directory这种最常见比如访问/index.php直接404Nginx错误日志里报文件找不到。检查站点配置文件里的root指令看指向的目录是不是你的项目实际所在目录。宝塔创建站点时默认的网站目录是/www/wwwroot/你的域名如果你把项目文件放到了/www/wwwroot/你的域名/public而root还是指向上级目录那/index.php在根目录下自然不存在。正确的做法是让root指向实际文件所在目录。比如项目在public目录下就在配置里写明server { listen 80; server_name 你的域名; root /www/wwwroot/你的域名/public; index index.php index.html; location ~ \.php$ { fastcgi_pass unix:/tmp/php-cgi-74.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }注意fastcgi_param SCRIPT_FILENAME这里用的是$document_root$fastcgi_script_name它把root指令的路径和请求的PHP路径拼在一起如果你改root不改这里PHP-FPM拿到的还是旧路径。宝塔里一般不直接改conf而是在站点设置里改“网站目录”面板会自动同步到配置里。3.2 场景BThinkPHP/Laravel这类框架的路由404框架类项目情况又不一样。打开首页通点某个模块的URL就404这种通常不是PHP文件缺失而是框架入口文件已经收到请求了但路由匹配不上。最典型的是ThinkPHP需要pathinfo模式或伪静态支持而Nginx默认配置根本没开pathinfo。伪静态加上的话规则要按框架官方文档来。ThinkPHP常规规则是location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s$1 last; } }这个规则的意思是如果请求的文件名在磁盘上不存在就把整个URL重写成/index.php?s原始路径交给后面的PHP location处理。Laravel的规则略有不同更推荐守卫式写法location / { try_files $uri $uri/ /index.php?$query_string; }try_files先尝试当前路径是否存在静态文件没有再尝试目录最后把请求交给/index.php。这么写比rewrite更稳不会产生重写循环。如果你不想用伪静态而是希望URL里直接带index.php/模块/控制器这种pathinfo形式那还需要额外配置PATH_INFO支持。在location ~ \.php段里加上location ~ \.php(.*)$ { fastcgi_split_path_info ^(.\.php)(.*)$; fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; }否则Nginx会把index.php/Home/Index中的/Home/Index丢掉或当成文件路径最后得到404。3.3 场景C动态页面在子目录和伪静态规则相互冲突还有一类情况是项目被放在站点子目录里比如/www/wwwroot/你的域名/phpmyadmin访问/phpmyadmin/index.php直接404。这种问题多半出在两个地方要么root没指对要么伪静态规则把子目录的URL也重写掉了。如果是子目录部署root应该保持站点根目录location匹配的时候要精确一点location ^~ /phpmyadmin/ { alias /www/wwwroot/你的域名/phpmyadmin/; location ~ \.php$ { fastcgi_pass unix:/tmp/php-cgi-74.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $request_filename; include fastcgi_params; } }这里用了alias而不是root注意SCRIPT_FILENAME参数也要跟着变否则PHP-FPM解析路径时会把alias和root混在一起报Primary script unknown。这个细节坑过不少人了。伪静态规则和子目录冲突的情况也很典型。比如布了一套ThinkPHP站点伪静态规则写的是全局匹配子目录下的真实PHP文件也会被这个规则捕获被重写到一个不存在的内部路径里。解决办法是把重写规则限定在框架入口文件对应的目录范围内别让location /一把梭。3.4 场景D换了域名或改了根目录旧配置还在起反作用这个场景在宝塔里很常见。用户复制了一个站点、改了新域名或者迁移数据后修改了网站目录但宝塔生成新配置文件时把旧站点的rewrite规则也一并带过来了。新域名打开动态页404一看rewrite文件里还有旧域名字符串或者root还是旧目录。排查时可以执行nginx -t验证配置语法但语法没错不代表逻辑对。我一般会打开站点配置文件直接看server_name、root、include的rewrite文件路径再打开rewrite文件看里面有没有写死域名或绝对路径。很多伪静态教程喜欢在rewrite规则里写死http://旧域名之类的地址换域名后必须同步修改否则重写出来的地址是错的访问自然404。4. 宝塔8.5里容易踩的坑和对应处理4.1 PHP版本和FPM监听通道对不上宝塔的一大特性是多PHP版本共存一个站点可以随时切换PHP版本。但版本切换后站点配置里的include enable-php-XX.conf不一定同步更新或者PHP-FPM进程没有完全重启新旧监听通道混杂。这种情况下访问动态页面要么404要么时好时坏非常迷惑。我的建议是站点遇到404先去站点设置里把PHP版本重新选择一遍保存后再看配置。面板会把include语句和fastcgi_pass通道一并修正。必要时去软件商店把对应PHP版本重启一下让Socket文件重新生成。这个操作比手动改配置安全得多因为宝塔在面板层面管理vhost文件手动改了容易被面板覆盖。4.2 目录权限和防跨站设置制造的假404宝塔默认给每个站点配置了open_basedir限制防止站点跨目录访问这个机制靠.user.ini文件实现存于站点根目录。如果你动过项目目录比如把应用从/www/wwwroot/你的域名挪到了/www/wwwroot/你的域名/app但.user.ini里的路径还是旧值PHP-FPM访问文件时会被open_basedir挡掉表现可能是403也可能是404取决于请求路径怎么拼接。处理方式很简单在站点设置里临时关闭“防跨站攻击”选项然后重新访问试一下。如果恢复正常就去删除或更新.user.ini文件。同时检查目录属主和权限目录需要755文件需要644或更宽松属主必须是www。很多用户习惯用root上传文件文件权限默认640www用户读不了PHP-FPM自然找不到脚本内容Nginx那边收到的就是404。这些检查可以用一条命令快速完成ls -la /www/wwwroot/你的域名 chown -R www:www /www/wwwroot/你的域名 chmod -R 755 /www/wwwroot/你的域名4.3 改了配置没真正生效伪静态缓存之类的隐性因素宝塔面板在站点设置里保存伪静态规则时理论上会reload Nginx。但如果你绕过面板直接编辑/www/server/nginx/conf/nginx.conf下次打开面板或者再次保存站点设置时你的修改可能被面板重新生成的配置覆盖。另外有些rewrite文件被include进主配置后Nginx的工作进程会持有旧配置句柄你以为reload过了其实没生效。我处理这类问题有个固定流程改完配置后先跑nginx -t验证语法再执行nginx -s reload重载最后用curl -I实测一下返回的响应头是不是新的。如果还是旧行为就把Nginx彻底重载一次命令是/www/server/nginx/sbin/nginx -t /www/server/nginx/sbin/nginx -s reload伪静态规则加载还有一个隐性因素CDN或浏览器缓存。如果访问的是公网站点CDN节点缓存了之前的404状态源站修好了但用户看到的还是404。排障时先用手机流量或curl绕过本地缓存访问一下避免白忙半天。4.4 OPcache把旧代码和旧路径缓存住了宝塔默认会安装OPcache扩展而且部分环境开启了validate_timestamps的定时检查策略。站点代码更新后如果路径还是旧路径OPcache可能还缓存着旧脚本的opcodePHP-FPM解析时拿到的还是旧路径进而产生奇怪的404或旧页面。这种情况不常见但遇到过两次。处理方式是在面板上把PHP重启或者手动清理缓存/etc/init.d/php-fpm-74 reload如果项目更新频繁建议在宝塔的PHP配置里把opcache.validate_timestamps开启并设置opcache.revalidate_freq0避免每次更新代码后还要重启PHP才能看到效果。5. 常见问题速查与几个排查过程中的土办法5.1 一张速查表对照解决大部分404我把排查过程中总结的现象、原因、处理方式整理成了一张表碰到类似问题可以先对照一下能节省不少时间。现象可能原因处理方式访问/index.php直接404错误日志提示No such file or directoryroot路径配置错误或文件不在对应目录检查站点配置的root确认项目文件真实位置伪静态访问404关掉伪静态后带index.php访问正常重写规则与框架不匹配换成框架官方伪静态规则或用try_files替代rewrite404日志中伴随Primary script unknownfastcgi_param的SCRIPT_FILENAME拼接错误调整fastcgi_param参数子目录场景改用$request_filenamePHP版本切换后动态页面404或时好时坏include的enable-php配置文件与PHP-FPM监听通道不一致站点设置里重新选择PHP版本并重启对应PHP改完伪静态规则依然404面板配置未生效或Nginx未reload执行nginx -t和nginx -s reload再curl验证目录迁移后404.user.ini里的open_basedir路径过期关闭防跨站攻击删除或更新.user.ini文件明明存在但提示404/403目录或文件权限不够www用户读不了chown -R www:www目录755文件644局域网用域名访问404用IP正常本地hosts或DNS解析问题检查hosts文件和站点server_name绑定5.2 我验证故障时常用的几个“土办法”排查动态404时我最常用的绝招就是临时写一个测试PHP文件。在站点根目录新建test.php内容就一行?php phpinfo();然后直接访问/test.php。如果这个文件能正常打开并输出PHP信息说明PHP-FPM链路是通的问题大概率出在伪静态或框架路由上。如果这个文件都404说明问题出在底层的Nginx location匹配或root路径上根本轮不到改伪静态。这一步能把排查范围瞬间缩小一半。第二个土办法是用curl直接测不要只盯着浏览器。命令curl -I http://你的域名/index.php看返回的HTTP状态码和响应头。如果响应头里能看到Server: nginx说明是Nginx层面返回的404如果能看到类似Server: nginx/1.x加上PHP信息说明PHP参与了解析。响应头里藏了很多真相。第三个土办法就更直白了把站点伪静态规则清空改成最简单的配置试一次。如果清空后动态页面恢复正常那说明是重写规则的问题如果还是404那就继续往底层挖。这个方法看起来粗暴但在排查重写规则导致的隐藏404时效率极高。结尾一点点个人体会踩过几次404的坑之后我最大的体会就是顺序很重要。以前我碰到404就一头扎进配置文件里疯狂改location改来改去越改越乱。后来固定成“日志 - FPM状态 - 站点配置 - 伪静态规则”这个顺序基本十分钟内就能定位问题不会再瞎折腾。最后再分享一个小技巧每次改动站点配置之前先把当前配置文件复制一份留着备份。改完如果不对自己对比改动内容很快就能知道哪里改坏了。宝塔面板虽然方便但它的配置生成机制对新手来说确实像个黑盒手上有备份心里不慌。这套排查方法不只适用于宝塔8.5任何Nginx PHP-FPM的环境换汤不换药思路一通到哪都能用。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →