A high-performance, automated hosting infrastructure designed for multi-tenant SaaS applications. This architecture uses Caddy at the edge to handle dynamic On-Demand TLS/SSL provisioning for unlimited custom domains, reverse-proxying clean traffic down to an isolated CloudPanel Nginx backend running a Laravel Core application.
In traditional multi-tenant SaaS platforms, allowing clients to map their own custom white-label domains (e.g., client-store.com) to your platform requires massive manual overhead: updating web server configs, manually triggering Let's Encrypt certificates, or using expensive enterprise third-party services.
This solution automates everything. When a custom client domain points its DNS records to this server:
- The server automatically negotiates an SSL certificate on the fly only if the domain is registered in the database.
- Traffic is securely passed down to a local, hardened Nginx wrapper.
- The Laravel application dynamically handles data routing based entirely on the host header.
[ Public Web Request ] ---> (Ports 80 / 443) ---> Caddy (Edge Proxy) | Queries Laravel API for Validation | Proxies Allowed Traffic <----------------------------------------+------------------------> | | v v (Port 8081) Nginx (Backend vHost) ---> PHP-FPM (Port 19001) ---> Laravel Core App | Tenant Domain Validation & Contextual Isolation
- Edge Ingress: The tenant's custom domain hits the server on ports
80or443. Caddy captures the request. - On-Demand Authentication: If Caddy hasn't seen this domain before, it pauses the TLS handshake and makes an internal callback to the Laravel API (
/api/v1/check-domain?domain=...). - Automated Provisioning: Laravel checks the database. If active, it returns a
200 OK. Caddy immediately spins up a valid, free Let's Encrypt/ZeroSSL certificate and completes the secure handshake with the user's browser. - Contextual Execution: Caddy reverse-proxies the decrypted traffic to Nginx on internal port
8081. Custom application middleware extracts the domain (getHost()) and safely scopes database queries so the user sees only their respective tenant data.
Caddy manages automated global throttling, HTTP/3 UDP buffering, and dynamic SSL handshakes using this concise structure:
{
# Throttle certificate requests to protect against DDoS/abuse
on_demand_tls {
ask http://127.0.0.1:8081/api/v1/check-domain
interval 2m
burst 5
}
}
# Catch-all entry point for all incoming web traffic
:443, :80 {
tls {
on_demand
}
# Proxy clean traffic to Nginx downstream
reverse_proxy 127.0.0.1:8081 {
header_up Host {http.request.host}
header_up X-Real-IP {http.request.remote.host}
header_up X-Forwarded-Proto {http.request.scheme}
}
}CloudPanel's Nginx configuration is updated to stand down from public ports and listen locally on port 8081, ensuring PHP environment variables reflect secure HTTPS states.
server {
listen 8081;
listen [::]:8081;
server_name yourdomain.com *.yourdomain.com;
root /home/cloudpanel-user/htdocs/yourdomain.com/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_intercept_errors on;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# CRITICAL: Tell PHP/Laravel the request is secure despite proxying over localhost
fastcgi_param HTTPS "on";
fastcgi_param SERVER_PORT 443;
fastcgi_pass 127.0.0.1:19001; # CloudPanel PHP-FPM socket
}
}A fast, lightweight endpoint written inside Laravel to handle Caddy's verification hook:
use App\Models\TenantDomain;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
Route::get('/v1/check-domain', function (Request $request) {
$domain = $request->query('domain');
if (empty($domain)) {
return response()->json(['error' => 'Domain parameter missing'], 400);
}
// Cache database checks to guarantee sub-millisecond responses to Caddy
$isValidTenant = Cache::remember("domain_check:{$domain}", 300, function () use ($domain) {
return TenantDomain::where('domain', $domain)
->where('is_active', true)
->exists();
});
if ($isValidTenant) {
return response()->noContent(200); // Instructs Caddy to issue the SSL
}
return response()->json(['error' => 'Unauthorized Domain'], 403);
});- System UDP Optimization: Extended kernel limits (
net.core.rmem_maxandnet.core.wmem_maxbumped to 2.5MB) to facilitate robust HTTP/3 over QUIC connections without dropped packets. - Database Isolation: Utilized Laravel middleware to automatically capture
$request->getHost(), decoupling global scope and applying tenant database prefixes instantly upon requests. - Rate-Limited SSL Handshakes: Implemented Caddy bursts rules (
burst 5 every 2m) preventing resource exhaustion if invalid or malicious domains are pointed at the server's public IP. - Trusted Proxy Middlewares: Configured Laravel's proxy arrays to explicitly trust
127.0.0.1preventing protocol confusion, ensuring secure cookies function perfectly.
To evaluate SSL handshake agreements locally without risking Let's Encrypt rate limits, execute the OpenSSL network testing client:
openssl s_client -connect 127.0.0.1:443 -servername dealers.yourdomain.com

