Scaling Caddy Server: How to Handle 20,000+ Customer Domains with On-Demand TLS on a Single VPS Cluster
Introduction: The Multi-Tenant Custom Domain Challenge
In modern SaaS, PaaS, and e-commerce platforms, allowing customers to point their own custom domains (e.g., shop.customer.com) to your infrastructure is a standard requirement. However, managing SSL/TLS certificates at scale for tens of thousands of unique domains introduces massive operational complexity. Traditional web servers like Nginx or Apache require complex configuration reloads and custom Let's Encrypt integration scripts that often fail or lag when hit with sudden traffic spikes.
Caddy Server revolutionizes this paradigm with its native On-Demand TLS feature. Instead of pre-provisioning certificates for thousands of domains, Caddy obtains a certificate automatically during the initial TLS handshake when a user visits the domain for the first time. While this works seamlessly out of the box for a few dozen sites, scaling it to handle over 20,000 active customer domains on a compact VPS cluster requires deliberate optimization. Without the right architectural guardrails, your cluster faces memory exhaustion, rate-limiting blocks from Let's Encrypt, and potential security vulnerabilities.
This comprehensive guide walks you through the engineering practices, configuration adjustments, and infrastructure patterns needed to optimize Caddy for heavy multi-tenant workloads.
1. Understanding the Core Architecture
Before diving into optimization, it is crucial to understand how Caddy handles On-Demand TLS at scale. When an unencrypted request hits Caddy, the following lifecycle occurs:
- The TLS Handshake Begins: A client requests a connection via a custom domain.
- The Asking Endpoint Check: Caddy intercepts the handshake and sends an internal HTTP request to a backend validator service to ask: "Is this domain authorized to use our platform?"
- Certificate Retrieval: If authorized, Caddy checks its local storage. If no valid certificate exists, it communicates with Let's Encrypt or ZeroSSL to issue one on the fly.
- Handshake Completion: The certificate is cached in memory, and the secure connection is established.
To support 20,000+ domains without degrading performance, every step of this lifecycle must be streamlined to prevent latency bottlenecks.
2. Implementing a Robust 'Ask' Endpoint (Security First)
The single most critical vulnerability of On-Demand TLS is the risk of Denial of Service (DoS) attacks. If malicious actors point thousands of random domains to your VPS IP address, Caddy will blindly attempt to request certificates for all of them, exhausting your Let's Encrypt rate limits and crashing your server. To prevent this, you must configure the ask parameter.
Architectural Rule: Never run On-Demand TLS in production without an internal validation endpoint. Caddy must only issue certificates for recognized, active customer domains.
Your Caddyfile block should look like this:
{
tls {
on_demand {
ask [http://internal-api.local/v1/validate-domain](http://internal-api.local/v1/validate-domain)
}
}
}Your backend validation service must be highly optimized. It should query a fast, indexed database (like a Redis cache or an optimized PostgreSQL table) and return a 200 OK status code if the domain is registered on your platform, or a 400/404 if it is not. Ensure this endpoint resolves within milliseconds to avoid adding overhead to the TLS handshake.
3. Distributed Storage Optimization for VPS Clusters
By default, Caddy stores certificates in the local file system. If you are scaling horizontally across a cluster of multiple VPS instances to ensure high availability, local storage is insufficient. If a customer request hits VPS #2 but the certificate was generated and stored on VPS #1, Caddy will attempt a redundant issuance, quickly hitting Let's Encrypt rate limits.
To solve this, you must compile Caddy with a distributed storage provider. Popular plug-and-play choices include:
- caddy-dns/redis: Uses a centralized Redis cluster for ephemeral caching and fast lookup.
- caddy-dns/s3: Stores certificates securely in an AWS S3-compatible object storage layer.
- Consul or Etcd plugins: Ideal for highly tightly synchronized cloud-native infrastructure.
By utilizing a shared backend storage system, any VPS node in your cluster can immediately fulfill a TLS handshake using a certificate requested by a sibling node, ensuring smooth, stateless scaling.
4. Fine-Tuning Kernel and Caddy Performance Limits
Handling 20,000+ domains implies a high volume of concurrent TCP connections. The default Linux kernel and Caddy settings need structural modifications to prevent connection drops under heavy loads.
Adjusting System File Descriptors
Every concurrent connection requires a file descriptor. Ensure your VPS OS can handle the volume by modifying /etc/security/limits.conf:
caddy soft nofile 65535
caddy hard nofile 65535Optimizing the Caddy Cache and Timeouts
Caddy keeps certificates in an in-memory cache to ensure lightning-fast handshakes. For 20,000 domains, optimize the memory usage and prevent connection hoarding by tweaking handshakes and buffer sizes within your global options configuration block:
{
servers {
timeouts {
read_body 10s
read_header 5s
write 10s
idle 2m
}
}
}These aggressive but reasonable timeouts ensure that dead or slow client connections are reaped quickly, freeing up precious memory and CPU cycles on your VPS instances.
5. Rate Limiting and Issuance Fail-Safes
Even with an ask endpoint, a sudden surge of new customers onboarding simultaneously can cause you to hit Let's Encrypt’s strict rate limits (e.g., duplicate certificate limits or failed validation limits). Implement these fail-safes to ensure maximum uptime:
- Dual Certificate Authorities (CAs): Configure Caddy to use both Let's Encrypt and ZeroSSL. If one CA throttles your cluster or undergoes an outage, Caddy will automatically failover to the alternative provider.
- Pre-routing validation: If possible, encourage customers to use a CNAME record pointing to your platform core domain (e.g.,
connect.yourplatform.com) rather than pointing an A record directly to a shifting VPS IP. This makes global traffic routing via Anycast or Cloudflare Load Balancers significantly cleaner.
Conclusion: A Scalable, Low-Maintenance Future
By combining Caddy’s native On-Demand TLS with an optimized internal verification system, distributed storage, and fine-tuned OS parameters, you can easily manage 20,000+ customer domains on modest VPS infrastructure. This setup eliminates the need for cron jobs, complex renewal scripts, or manual intervention, allowing your engineering team to focus on building core features while Caddy handles edge security flawlessly.
