Skip to content

Moskov-1/on-demand-ssl-on-cloudpanel

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Dynamic Multi-Tenant SaaS Infrastructure

Header

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.


🚀 The Core Problem Solved

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:

  1. The server automatically negotiates an SSL certificate on the fly only if the domain is registered in the database.
  2. Traffic is securely passed down to a local, hardened Nginx wrapper.
  3. The Laravel application dynamically handles data routing based entirely on the host header.

🛠️ Technical Architecture & Workflow

Architecture

[ 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

The 4-Step Lifecyle of a Tenant Request

Lifecycle

  1. Edge Ingress: The tenant's custom domain hits the server on ports 80 or 443. Caddy captures the request.
  2. 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=...).
  3. 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.
  4. 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.

💻 Code Configurations & Implementation

1. The Edge Server (/etc/caddy/Caddyfile)

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}
    }
}

2. The Internal Web Server Layer (/etc/nginx/sites-enabled/saas.conf)

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
  }
}

3. The Tenant Guard Controller (routes/api.php)

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);
});

⚡ Performance Optimization & Security Hardening

  • System UDP Optimization: Extended kernel limits (net.core.rmem_max and net.core.wmem_max bumped 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.1 preventing protocol confusion, ensuring secure cookies function perfectly.

🛠️ Local Verification & Diagnostics

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

About

This repository gives an overview of how a nginx proxy can be easily substituted with a caddy service to allow on demand ssls.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors