Adding custom domains to a Laravel app is two real steps — resolve the tenant from the host, and trust your proxy so the framework sees the right client IP and scheme — plus SSL, which lives in front of Laravel. Here is the DIY path and the one-call shortcut.
// app/Http/Middleware/ResolveTenant.php
public function handle(Request $request, Closure $next)
{
$host = $request->getHost();
if (str_ends_with($host, 'yourapp.com')) {
return $next($request);
}
$tenant = Tenant::where('domain', $host)->first();
abort_if(! $tenant, 404); // fail closed
app()->instance('tenant', $tenant);
return $next($request);
}Step 1 — Point DNS at your app
app.customer.com. CNAME proxy.yourapp.com.Apex domains need an A record or ALIAS — see the CNAME-at-apex problem. Verify with the CNAME checker.
Step 2 — Route by host
Register the middleware globally (or on your tenant route group) so every controller can resolve app('tenant'). For a fixed set of patterns you can also use a route constraint:
Route::domain('{tenant}.yourapp.com')->group(function () {
Route::get('/', [SiteController::class, 'show']);
});But for arbitrary customer-owned domains, the database-backed middleware is the right tool — Route::domain() is for known patterns, not thousands of distinct domains.
Step 3 — The TrustProxies gotcha
Behind a load balancer or edge proxy, Laravel ignores X-Forwarded-* headers unless you trust the proxy. Without it, $request->ip() is your edge and $request->isSecure() can be wrong, breaking redirects and rate limits:
// bootstrap/app.php (Laravel 11+)
->withMiddleware(function (Middleware $middleware) {
$middleware->trustProxies(
at: explode(',', env('EDGE_CIDRS', '')),
headers: Request::HEADER_X_FORWARDED_FOR
| Request::HEADER_X_FORWARDED_HOST
| Request::HEADER_X_FORWARDED_PROTO,
);
})Trust specific edge ranges. Only use '*' if your app can never be reached directly, bypassing the proxy.
Step 4 — SSL in front of Laravel
Terminate TLS with Nginx/Caddy + ACME, or a custom-domains service. Caddy's on-demand TLS issues per-hostname behind an allow-list; trade-offs in cnames.dev vs self-hosting Caddy on-demand TLS.
The one-API-call way
curl -X POST https://api.cnames.dev/v1/domains \
-H "Authorization: Bearer sk_live_..." \
-d '{"hostname":"app.customer.com","origin":"your-laravel-app.internal"}'cnames.dev issues and renews the certificate and forwards the real client IP; your Laravel middleware and TrustProxies config stay as-is. Popular with agencies hosting client sites. Full details in the complete guide.