route::domain()是唯一按host头匹配子域名的机制,必须用纯域名(如api.example.com)、配好hosts和web服务器、显式接收动态参数、配合where()限制、手动干预url生成、确保路由顺序前置并清缓存。

Route::domain() 是唯一能真正按 Host 头匹配子域名的机制,其他写法(比如 Route::group(['domain' => ...]) 或中间件判断)都不触发 Laravel 的域名路由逻辑,请求会直接 fallback 到后续路由或 404。
Route::domain() 必须写纯域名,不能带协议或路径
常见错误是把 https://api.example.com 或 api.example.com/v1 塞进 Route::domain(),结果永远不匹配。Laravel 只比对 HTTP 请求头里的 Host 字段,且只接受形如 api.example.com 或 {sub}.example.com 的纯域名字符串。
-
Route::domain('api.example.com')✅ 匹配固定子域 -
Route::domain('{sub}.example.com')✅ 支持动态捕获 -
Route::domain('https://api.example.com')❌ 协议被忽略,整个字符串不匹配 -
Route::domain('api.example.com/api')❌ 路径部分无效,匹配失败
动态子域名参数必须显式接收,否则拿不到值
写 {sub} 只是声明了变量名,不等于自动注入。闭包或控制器方法签名里必须显式列出该参数,否则 request()->route('sub') 返回 null,甚至抛出 MissingParameterException。
- 闭包中要这样写:
function ($sub) { return "Sub: $sub"; } - 控制器方法也要加参:
public function index($sub) { ... } - 若用
where()限制合法值(如->where('sub', 'shop|admin|api')),能防住hacker.example.com这类意外匹配 - 参数名必须和路由定义中的一致,
{tenant}就得用$tenant接收,不能混用
Web 服务器和 hosts 必须先配好,否则请求根本进不了 Laravel
这是最常被跳过的前提:Laravel 不监听域名,它只处理已被 Web 服务器转发过来的请求。如果 Nginx/Apache 没把 admin.example.com 的请求指向 public/,你就只会看到 404 或欢迎页——那不是 Laravel 报的错。
- Nginx 配置中,
server_name必须包含通配符或显式列出子域,例如:server_name example.com *.example.com; - 本地开发时,
/etc/hosts(macOS/Linux)或C:\Windows\System32\drivers\etc\hosts(Windows)必须加多行,如:127.0.0.1 admin.example.com api.example.com shop.example.com - HTTPS 场景下,所有子域名必须有有效 SSL 证书(推荐泛域名证书
*.example.com),否则浏览器或 Web 服务器会在 TLS 握手阶段就拒绝连接
url() 和 route() 默认不认子域名,必须手动干预
即使你正访问 api.example.com,route('user.show', 1) 默认仍输出 https://example.com/user/1 —— 因为 Laravel 完全忽略当前请求的 Host,只看 APP_URL 配置。
- 最简方案:在
AppServiceProvider@register()中加\URL::forceRootUrl(\Request::getSchemeAndHttpHost()); - 若用了反向代理(如 Nginx 传了
X-Forwarded-Host),需确保TrustProxies中配置了可信 IP,否则getSchemeAndHttpHost()可能返回内网地址 - 重定向不能依赖
redirect()->route()自动换域,得显式拼完整地址,或用url()生成 - 路由顺序影响 fallback:
Route::domain()分组必须写在通用路由(如Route::get('/'))之前,否则子域名请求会被前者提前捕获
Host 请求头是否到达 Laravel,而不是怀疑路由写法。











