ccnames.dev

cnames.dev / blog

Tutorial12 min read

How to add custom domains to your Laravel app

Custom domains in Laravel: Route::domain constraints, the TrustProxies middleware for real client IPs behind a proxy, and SSL. DIY and the one-API-call way.

S
Saeed
July 1, 2026

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.

Building a SaaS that needs custom domains? cnames.dev gives every customer their own domain — SSL, edge routing, and per-tenant isolation in one API call. Free for 25 domains, no demo call. Start free · Read the docs

Frequently asked questions

How do I route custom domains in Laravel?

Two ways: a global middleware that reads $request->getHost() and binds the tenant (best for wildcard customer domains), or Route::domain() constraints for a fixed set of patterns. For arbitrary customer domains, use the middleware approach with a database lookup.

Why is $request->ip() wrong behind a load balancer or proxy?

Laravel only reads X-Forwarded-* headers from proxies you explicitly trust. Configure the TrustProxies middleware (set $proxies to your edge IPs or "*" only if the app is never reachable directly) so ip(), getHost(), and isSecure() reflect the real client.

Does Laravel issue SSL certificates?

No. TLS is terminated by your web server or a proxy in front (Nginx, Caddy) or by a custom-domains service. Laravel receives the decrypted request plus forwarded headers.

Keep reading

S

SaeedFounder, cnames.dev — runs custom-domain infra for 5,000+ production sites at Lindo.ai.