thinkphp子目录部署路由失效的根本原因是public/index.php中$_server['script_name']未正确反映真实访问路径,导致app_url推导错误;需在index.php中显式设置$_server['app_url']为带子目录的完整url,并确保web服务器重写规则(如nginx的alias或apache的rewritebase)同步匹配子目录上下文。

ThinkPHP 路由不生效,其实是入口文件没对齐子目录路径
绝大多数子目录部署下路由失效,根本原因不是路由配置错,而是 public/index.php 里 $_SERVER['SCRIPT_NAME'] 或手动写的 __ROOT__ 没反映真实访问路径。ThinkPHP 6+ 默认靠 $_SERVER['SCRIPT_NAME'] 推导 APP_URL 和路由匹配前缀,一旦服务器把请求代理进子目录(比如 https://example.com/myapp/),而 index.php 还按根目录逻辑跑,路由就永远匹配不到 /myapp/user/list 这类带子目录前缀的 URL。
- 确认你访问的是
https://domain.com/subdir/,不是https://domain.com/subdir/public/—— 后者会直接绕过重写规则,index.php拿到的SCRIPT_NAME是/subdir/public/index.php,但框架却以为你在根目录 - 检查 Apache 的
.htaccess或 Nginx 的location是否把请求正确 rewrite 到public/index.php,且保留了完整 URI(Nginx 要用try_files $uri $uri/ /subdir/public/index.php?$query_string;) - 临时在
public/index.php顶部加var_dump($_SERVER['SCRIPT_NAME'], $_SERVER['REQUEST_URI']); die;,看输出是否含子目录名 —— 如果SCRIPT_NAME里没有/subdir,说明 Web 服务器没传对,得调配置;如果有了但路由仍不匹配,往下看
TP6+ 必须显式设置 APP_URL 才能支持子目录
ThinkPHP 6.0 开始,APP_URL 不再自动从 SCRIPT_NAME 推导,而是默认为 http://localhost,这会导致生成的 URL(包括路由匹配时的 base)完全脱节。你不设它,框架就“看不见”子目录。
- 在
public/index.php中,在require __DIR__.'/../vendor/autoload.php';之后、(new App())->run()->send();之前,加上:$_SERVER['APP_URL'] = 'https://yourdomain.com/subdir';
- 更稳妥的做法是动态拼接:
$scheme = $_SERVER['HTTPS'] ?? 'http';<br>$host = $_SERVER['HTTP_HOST'] ?? 'localhost';<br>$subdir = str_replace(basename($_SERVER['SCRIPT_NAME']), '', $_SERVER['SCRIPT_NAME']);<br>$_SERVER['APP_URL'] = $scheme . '://' . $host . rtrim($subdir, '/');
(注意:$subdir结尾有斜杠,rtrim是为了去掉末尾 /,避免生成//subdir) - 别改
config/app.php里的url_common或url_html_suffix—— 它们和子目录无关,只影响 URL 格式,不解决基准路径错位
Apache .htaccess 和 Nginx location 配置必须显式声明子目录上下文
重写规则本身不感知子目录,你得告诉它“我当前在 /subdir 下”,否则 rewrite 后的 REQUEST_URI 会丢掉子目录段,导致路由解析时找不到对应规则。
当代理已经知道网站路由或内容URL,并且在启动前需要有效的sitemap XML、sitemap索引或robots.txt引用时,请使用sitemap。这是一个发布构件技能,而不是爬虫或SEO平台。
- Apache:
.htaccess放在public/目录下,开头加RewriteBase /subdir/(注意结尾斜杠),否则RewriteRule匹配的是去掉了/subdir的路径 - Nginx:不要用
root /path/to/project/public;,改用alias /path/to/project/public/;,并确保location /subdir/ { ... }块里try_files的 fallback 路径写成/subdir/public/index.php,而不是/public/index.php - 验证方法:访问一个不存在的路由如
/subdir/xyz,看返回的是 TP 的 404 页面(说明重写生效且进了框架),还是 Web 服务器的 404(说明 rewrite 没触发或路径错)
路由定义本身不用改,但生成 URL 时要小心 url() 函数行为
只要 APP_URL 设对、重写规则到位,所有 Route::get('user/list', ...) 这类定义照常工作,无需加前缀。但调用 url('user/list') 生成链接时,结果是否带子目录,取决于 APP_URL 和当前请求的 host/path 是否一致。
- 如果
APP_URL是https://a.com/subdir,url('user/list')输出的就是https://a.com/subdir/user/list - 如果页面是通过
https://a.com/subdir/admin访问的,但APP_URL写成了https://b.com,那生成的链接就会跨域,且路由匹配时因 host 不同可能失败 - 开发环境常用
php think run,它默认监听127.0.0.1:8000,此时子目录部署无效 —— 本地调试务必用真实 Web 服务器(Apache/Nginx)配好子目录,不然永远复现不了线上问题
最常被忽略的是:Web 服务器配置和 PHP 入口文件的路径推导必须同步,差一个字符(比如少个 / 或多一个 public)都会让路由系统“看到”的请求路径和实际浏览器地址对不上。这种错位不会报错,只会静默 404 —— 查的时候得从 $_SERVER 变量一层层打出来看,而不是盯着路由定义反复修改。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










