You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Caddy的多租户SaaS域名映射失效及Laravel路由问题排查

解决Laravel多租户SaaS自定义CNAME域名路由匹配问题

问题根源

你的路由规则硬绑定了{subdomain_slug}.{primary_domain}格式的域名,当客户通过CNAME映射自有域名(如support.waitlist.guru)时,请求的Host头是该自定义域名,而非应用主域名的子域格式,导致Laravel无法匹配路由,最终返回404或跳转到主域首页。

解决方案

1. 移除路由的固定域名约束

修改路由配置,去掉domain()的硬绑定,转而通过中间件动态识别子域:

// 移除原路由的domain约束,通过中间件处理子域识别
Route::middleware(['appendSubdomainInfo', 'subdomainAccessChecker'])->group(function () {
    Route::get('/check', function($subdomain_slug) {
        dd($subdomain_slug);
    });

    // 其他子域路由...
});

2. 更新中间件实现动态子域映射

首先在数据库新增domain_bindings表,存储客户自定义域名与应用子域的对应关系(例如support.waitlist.guru对应waitlist),然后修改appendSubdomainInfo中间件,根据请求Host查询映射关系:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use App\Models\DomainBinding;

class AppendSubdomainInfo
{
    public function handle(Request $request, Closure $next)
    {
        $host = $request->getHost();
        $subdomainSlug = null;

        // 优先匹配自定义域名绑定
        $binding = DomainBinding::where('custom_domain', $host)->first();
        if ($binding) {
            $subdomainSlug = $binding->subdomain_slug;
        } 
        // 处理原生应用子域(如waitlist.layoff.wtf)
        else {
            $domainParts = explode('.', $host);
            $primaryDomain = config('app.primary_domain');
            if (count($domainParts) >= 3 && implode('.', array_slice($domainParts, -2)) === $primaryDomain) {
                $subdomainSlug = $domainParts[0];
            }
        }

        // 将子域信息注入请求和路由参数
        if ($subdomainSlug) {
            $request->attributes->set('subdomain_slug', $subdomainSlug);
            $request->route()->setParameter('subdomain_slug', $subdomainSlug);
        }

        return $next($request);
    }
}

3. 确保Caddy正确传递Host头

显式配置Caddy的php_fastcgi模块,保证Laravel能获取到真实的客户域名:

{
        on_demand_tls {
                ask http://layoff.wtf/caddy/ask
                interval 2m
                burst 5
        }
}

https:// {
        tls {
                on_demand
        }

        root * /home/forge/layoff.wtf/public
        file_server
        php_fastcgi unix//run/php/php8.2-fpm.sock {
                env HTTP_HOST {host}
        }
}

4. 完善子域权限验证

在subdomainAccessChecker中间件中,验证子域的合法性:

public function handle(Request $request, Closure $next)
{
    $subdomainSlug = $request->attributes->get('subdomain_slug');

    if (!$subdomainSlug) {
        // 无有效子域,跳转主域或返回403
        return redirect(config('app.url'));
    }

    // 验证子域对应的租户是否存在且可用
    $tenant = \App\Models\Tenant::where('slug', $subdomainSlug)->first();
    if (!$tenant || !$tenant->is_active) {
        abort(403, 'Invalid or inactive subdomain');
    }

    // 将租户实例注入请求,供后续业务逻辑使用
    $request->attributes->set('tenant', $tenant);

    return $next($request);
}

5. 客户CNAME配置说明

告知客户只需将自定义子域通过CNAME记录指向对应应用子域(如support.waitlist.guru CNAME到waitlist.layoff.wtf),无需额外配置,应用会自动识别并路由到对应子域内容。


内容的提问来源于stack exchange,提问作者TheBigK

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 09:26:02