ThinkPHP 的路由分发与多数 PHP 框架略有不同,它习惯将所有请求先交给入口文件 index.php,再由框架从 URL 中解析模块、控制器和操作。Apache 环境可以通过 .htaccess 很轻松地隐藏 index.php,但 Nginx 并不读取 .htaccess,也不会像 Apache 那样自动处理 PATHINFO。如果只配置了 PHP 解析,访问 /index.php/home/news 这类地址可能正常,一旦改成 /home/news 就很容易出现 404。因此,所谓 Nginx 配置 ThinkPHP 伪静态,本质就是增加一条兜底规则:当请求不是真实文件或目录时,交给 index.php 并把原始路径以 s 参数的形式传进去。理解了这条主线,后续配置和排错都会清晰很多。

核心规则:rewrite 与 try_files 两种写法
Nginx 中实现 ThinkPHP 伪静态最常见的是 rewrite 判断写法。它的思路很简单:先判断当前请求路径是否对应服务器上的真实文件或目录,如果不是,就重写到 index.php,并通过 s 参数携带地址。典型配置如下:
location / {
if (!-e $request_filename) {
rewrite ^(.*)$ /index.php?s=$1 last;
}
index index.php index.html;
}
这段配置中 if (!-e $request_filename) 用来检测文件或目录是否存在,-e 表示只要存在文件或目录就为真,取反后就是不存在的请求。rewrite 指令将任意路径交给 /index.php,并把路径放入 s 参数。last 表示完成当前级别的匹配后重新发起内部跳转,而不是直接返回给客户端。这个写法在 ThinkPHP 3.x 时代非常普遍,优点是直观、按条件判断,缺点是使用了 if 指令,在高并发场景下可能带来少量性能损耗,而且 if 在 Nginx 中属于比较容易被误用的模块。
更推荐的写法是使用 try_files,它不需要 if,执行路径更清晰,Nginx 官方也倾向于这种写法:
location / {
try_files $uri $uri/ /index.php?s=$uri&$args;
}
try_files 会按顺序尝试三个候选项:先找文件 $uri,再找目录 $uri/,如果都不存在,执行最后一个内部重写。这里把请求 URI 和查询字符串一起传给 index.php,s 参数保存路径,$args 保留原查询串,因此 /news/5?page=2 这种地址也能正确解析。由于避免 if 判断,try_files 是现代 Nginx 配置的基础写法,也适合 ThinkPHP 5 和 ThinkPHP 6。使用该写法时要注意 $uri 已经经过 Nginx 规范化,不包含查询字符串,所以需要额外拼接 $args。
ThinkPHP 版本差异与 URL 模式的关系
ThinkPHP 3.x、5.x、6.x 对伪静态的支持细节并不完全一致。3.x 项目通常在配置文件中设置 URL_MODEL 为 2 或 3。URL_MODEL 等于 2 表示 REWRITE 模式,隐藏入口文件;等于 3 表示兼容模式,专门用于不支持 PATHINFO 的服务器。Nginx 使用 s 参数正是兼容模式的一种体现。3.2 官方手册给出的 Nginx 规则就是 rewrite ^(.*)$ /index.php?s=$1 last;,因此老项目维护时可以直接沿用。
ThinkPHP 5 和 6 进一步弱化了入口文件概念,但为了兼容各种服务器,仍然保留了 var_pathinfo 配置,默认值就是 s。也就是说,当请求变成 /index.php?s=/home/news 时,框架会读取 s 参数并恢复出 PATHINFO 数据。因此 Nginx 把 /home/news 重写为 /index.php?s=/home/news,在 ThinkPHP 5/6 中通常可以正常工作。需要注意的是,如果项目使用了自定义 pathinfo 变量名,或者在应用中间件里对 $_SERVER['REQUEST_URI'] 做了严格校验,就需要同步检查这个 s 参数的映射关系。
如果你希望保留真正的 PATHINFO 模式,而不是使用 s 参数,就需要在 PHP 解析段加入 fastcgi_split_path_info 和 PATH_INFO 传递。配置方式大致如下:
location ~ \.php {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_pass 127.0.0.1:9000;
}
这种配置对 Nginx 版本和 PHP-FPM 参数要求更严格,而且不同发行版对 fastcgi_params 的默认内容有差异,排错成本高于 s 参数方案。对于大多数业务场景,直接使用 s 参数兼容模式已经足够,不必刻意追求原生 PATHINFO。相较之下,s 参数更容易排查,也更少出现 No input file specified 这类问题。
完整 server 配置与常见错误排查
只看 location / 片段容易漏掉 PHP 解析和静态资源放行,实际部署时建议直接使用完整 server 配置。以下是一个 ThinkPHP 5/6 项目的最小可用示例,项目根目录指向 public:
server {
listen 80;
server_name ipipp.com;
root /var/www/tp/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?s=$uri&$args;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass 127.0.0.1:9000;
}
location ~ /\.ht {
deny all;
}
}
这个配置中最关键的一点是 root 必须指向入口文件所在的 public 目录。ThinkPHP 默认把 index.php 放在 public 下,如果 root 错误地指向项目根目录,Nginx 执行内部跳转到 /index.php 时找不到文件,就会返回 No input file specified 或 404。因此出现首页能访问、内页报错的情况时,第一件事应确认 root 路径和目录结构。
另一个常见问题是 location 匹配顺序。Nginx 首先匹配前缀 location /,但对于 .php 结尾的请求,正则 location ~ \.php$ 会优先执行。配置伪静态后,/home/news 会先被 location / 捕获并内部重写到 /index.php?s=/home/news,随后再次进入 location 匹配阶段,这次请求以 index.php 结尾,由 PHP 解析段处理。如果 PHP 段缺少 fastcgi_pass 或 include fastcgi_params,就会被当成普通文件下载,或者出现 502。排查时可以用 curl -I 查看响应头,也可以打开 Nginx error.log,观察 rewrite 后的实际 URI。
还有一类问题与 ThinkPHP 应用自身有关:Nginx 已经正确重写,但页面仍显示 404。此时应查看应用是否开启了路由和重写模式。ThinkPHP 5/6 中需要确认 config/app.php 中的 url_route_on 是否为 true,以及是否在路由中定义了对应规则。如果 URL 模式仍为普通模式,框架仍会尝试以 index.php 作为入口,这时可能出现入口重复或路径解析失败。修改配置后建议清理 runtime 缓存,避免旧的 URL 规则继续生效。
安全加固与性能细节
伪静态配置不仅要把请求交给框架,还要防止非脚本文件被当作 PHP 执行。上传目录、静态资源目录如果落入 PHP 解析范围,可能带来严重的安全风险。可以在 server 中增加针对 uploads 目录的规则,直接禁止 PHP 执行:
location ~* ^/(uploads|assets)/.*\.php$ {
deny all;
}
上述正则中的反斜杠用于转义点号,表示只匹配真实文件名中的 .php 后缀,而不是任意字符加 php。对于静态文件,推荐使用 location ~* \.(css|js|jpg|png|gif|webp)$ 设置长缓存,Nginx 会优先按正则匹配静态资源,减少进入 ThinkPHP 的请求数量。但要注意,这段正则同样需要正确转义,否则可能影响到正常 PHP 请求。
性能方面,try_files 已经足够轻量。不要在 location / 前面叠加复杂的 if 判断,也不要为了隐藏 index.php 加多层 rewrite。每多一层规则,Nginx 的匹配成本都会增加。对于高流量站点,还可以把 ThinkPHP 的 runtime 目录放到与 public 同级的可写目录,并在 Nginx 中禁止外部访问,避免缓存文件暴露。日常维护时,改完配置先用 nginx -t 测试语法,再执行 nginx -s reload,可以避免因语法错误导致服务中断。
总体而言,Nginx 配置 ThinkPHP 伪静态的难点不在规则本身,而在于理解请求的内部跳转过程。只要抓住“文件或目录不存在就交给 index.php”这一条,再结合 ThinkPHP 的 s 参数兼容机制,就能应对大多数部署版本和场景。