
Dec 23, 2025
For years, setting up a web server with HTTPS was a multi-step nightmare:
Install Nginx
Install Certbot (SSL certificate tool)
Run Certbot to get a certificate
Configure Nginx to use the certificate
Set up a cron job to renew the certificate before it expires
Hope the cron job runs successfully
When it fails silently, your site goes down
Spend hours debugging why HTTPS is broken
This process is fragile, error-prone, and requires constant maintenance.
Then Caddy came along and asked: "Why should this be so complicated?"
Caddy is a modern web server that makes HTTPS automatic. You don't configure certificates or renewal. You just point your domain at your server and Caddy handles everything.
In this guide, we'll explore what Caddy is, why it's revolutionary, and why Node.js developers should care.
Before we explain Caddy, let's clarify what a web server does.
When you visit a website, here's what happens:
You visit: https://example.com
↓
Your browser connects to the server
↓
[Web Server receives the request]
↓
Web Server routes the request:
- If requesting a static file (CSS, JS, images)
→ Serve from disk
- If requesting dynamic content
→ Forward to backend application
↓
Response sent back to browser
↓
You see the website
A web server's job:
Listen for incoming connections on port 80 (HTTP) and 443 (HTTPS)
Terminate HTTPS connections (decrypt the traffic)
Route requests to the right place (static files or your app)
Manage certificates and security
| Server | Use Case | Configuration | HTTPS Setup |
|---|---|---|---|
| Apache | Everything, legacy | Complex XML | Manual, Certbot |
| Nginx | Performance, scaling | Moderate, text-based | Manual, Certbot |
| Caddy | Modern apps | Simple, intuitive | Automatic! |
| Lighttpd | Lightweight | Simple | Manual, Certbot |
| Node.js | Backend apps | Programmatic | Should not handle HTTPS directly |
Caddy is a modern web server and reverse proxy built from the ground up with three core principles:
Security first: HTTPS is the default, not an afterthought
Simplicity: Configuration should be readable and straightforward
Automation: Tedious tasks like certificate management should be automatic
Think of Caddy as:
Nginx's modern cousin: Similar role, but designed for 2020s best practices
Apache's replacement: Simpler, faster, less configuration
Your Node.js app's bodyguard: Sits in front and protects your app
| Metric | Value |
|---|---|
| Release Date | 2015 |
| Current Version | 2.x (stable) |
| Language | Go (compiled, fast, single binary) |
| Memory Usage | ~10-30 MB (vs Nginx ~5-10, Apache ~20-50) |
| Configuration Simplicity | 10 lines for complex setup (vs Nginx 50+) |
| Auto-renewal Success Rate | 99%+ (vs manual cron jobs ~95%) |
Traditional workflow (Nginx + Certbot):
# Step 1: Install Nginx
sudo apt install nginx
# Step 2: Install Certbot
sudo apt install certbot python3-certbot-nginx
# Step 3: Get certificate (manual command)
sudo certbot certonly --nginx -d example.com
# Step 4: Configure Nginx to use certificate
sudo nano /etc/nginx/sites-available/example.com
# Edit with SSL paths...
# Step 5: Reload Nginx
sudo systemctl reload nginx
# Step 6: Set up auto-renewal cron job
sudo crontab -e
# Add: 0 3 * * * /usr/bin/certbot renew --quiet
# Step 7: Hope nothing breaks
# In reality: Something WILL break eventually
What can go wrong:
Certificate expires because cron job fails
Cron job runs but renewal is incomplete
Nginx configuration breaks during reload
Certificate path changes and nothing finds it
DNS isn't configured correctly and certificate validation fails
Caddy workflow:
# Step 1: Install Caddy (already handles HTTPS)
sudo apt install caddy
# Step 2: Create Caddyfile
echo 'example.com { reverse_proxy localhost:3000 }' | sudo tee /etc/caddy/Caddyfile
# Step 3: Reload Caddy
sudo systemctl reload caddy
# Done! HTTPS is automatic, certificates are renewed automatically.
What Caddy does automatically:
✅ Detects domain name from configuration
✅ Obtains certificate from Let's Encrypt
✅ Renews certificate before expiry
✅ Updates configuration without downtime
✅ Handles edge cases and failures
Result: Zero maintenance, 99%+ uptime guarantee.
What it does:
Automatically obtains SSL certificates
Automatically renews before expiry
Zero configuration needed
Traditional Nginx approach:
# Manual certificate request
certbot certonly --nginx -d example.com
# Manual renewal setup
0 3 * * * certbot renew
# Hope renewal works!
Caddy approach:
example.com {
reverse_proxy localhost:3000
}
# Certificate is obtained and renewed automatically!
Nginx configuration (confusing):
server {
listen 80;
listen [::]:80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Caddy configuration (clear and simple):
example.com {
reverse_proxy localhost:3000
}
That's it. Caddy handles:
HTTP → HTTPS redirect
SSL certificate obtaining and renewing
Security headers
Proper proxy headers
HTTP/2
A reverse proxy forwards requests to backend applications. Caddy does this cleanly:
# Forward all requests to Node.js app
example.com {
reverse_proxy localhost:3000
}
# Forward specific paths to different apps
api.example.com {
reverse_proxy localhost:3001 # API server
}
admin.example.com {
reverse_proxy localhost:3002 # Admin panel
}
When you update Caddy's configuration, you can reload without disconnecting users:
# Old way (Nginx restart): Users get brief downtime
sudo systemctl restart nginx
# Caddy way (reload): Zero downtime
sudo systemctl reload caddy
# or
sudo caddy reload --config /etc/caddy/Caddyfile
Existing connections stay alive while new configuration is loaded.
Caddy prioritizes security by default:
| Feature | Caddy Default | Nginx Default |
|---|---|---|
| HTTPS | ✅ Automatic | ❌ Manual setup |
| HTTP/2 | ✅ Enabled | ⚠️ Must enable |
| TLS Version | ✅ 1.2+ only | ⚠️ Older versions by default |
| Security Headers | ✅ Sensible defaults | ❌ Must configure |
| HSTS | ✅ Automatic | ❌ Manual setup |
You get security best practices automatically, not as an afterthought.
Caddy is written in Go and compiles to a single binary:
# One file to download and run
# No dependencies, no complexity
wget https://github.com/caddyserver/caddy/releases/download/v2.x.x/caddy_linux_amd64
chmod +x caddy_linux_amd64
./caddy_linux_amd64 serve
Compare to Nginx:
# Requires multiple dependencies
sudo apt install nginx
# Which installs: libc, openssl, zlib, pcre, etc.
# 50+ MB of dependencies
| Scenario | Why Caddy |
|---|---|
| Node.js deployment | HTTPS automatic, simple config |
| New projects | Faster setup, less config |
| Small/medium teams | Easier to understand and maintain |
| Simplicity matters | Caddyfile is much easier to read |
| Automatic renewal | No manual certificate management |
| Development servers | Quick setup with HTTPS |
| Scenario | Why Nginx |
|---|---|
| High-traffic (1M+ requests/sec) | Proven at massive scale |
| Extreme customization | More control over every detail |
| Legacy systems | Older infrastructure uses Nginx |
| Performance critical | Tuned for maximum performance |
| Large ecosystem | More third-party modules available |
| Feature | Caddy | Nginx |
|---|---|---|
| HTTPS Setup | Automatic (5 min) | Manual + Certbot (30 min) |
| Configuration Complexity | Simple | Moderate to complex |
| Learning Curve | 1-2 hours | 4-8 hours |
| Memory Usage | ~20 MB | ~10 MB (lighter) |
| CPU Usage | Low | Very low |
| Configuration Reload | Zero downtime | Graceful reload |
| API Documentation | Modern | Limited |
| Community Size | Growing | Huge |
| Enterprise Support | Available | Available |
| Typical Setup Time | 15 minutes | 1-2 hours |
| Certificate Renewal | Automatic | Via cron (fragile) |
| Reverse Proxy | Built-in, easy | Built-in, complex |
| Rate Limiting | Built-in | Requires modules |
| WebSocket Support | Built-in | Requires config |
Goal: Run a Node.js API behind HTTPS with zero hassle
Why Caddy:
api.example.com {
reverse_proxy localhost:3000
}
That's it. Node.js app runs on port 3000, Caddy handles HTTPS, certificates, and everything.
With Nginx + Certbot:
15+ configuration lines
Manual certificate setup
Scheduled renewal
Potential for things to break
Goal: Run multiple backends on different ports
Caddy:
api.example.com {
reverse_proxy localhost:3000
}
auth.example.com {
reverse_proxy localhost:3001
}
admin.example.com {
reverse_proxy localhost:3002
}
Nginx:
Separate server blocks
More configuration
More potential for errors
Goal: Test HTTPS locally before production
Caddy:
caddy file-server
# Serves HTTPS on localhost with auto-generated certificate
# Perfect for testing
Nginx:
Must manually create self-signed certificates
More setup required
Goal: Add security headers to protect against XSS, clickjacking, etc.
Caddy:
example.com {
header {
X-Frame-Options "DENY"
X-Content-Type-Options "nosniff"
Strict-Transport-Security "max-age=31536000"
}
reverse_proxy localhost:3000
}
Nginx:
More verbose configuration
Requires knowledge of each header
Longer setup
When you start Caddy, it reads the Caddyfile and detects domain names:
example.com { # ← Caddy detects this domain
reverse_proxy localhost:3000
}
Caddy uses ACME (Automatic Certificate Management Environment) protocol to prove ownership of the domain:
Caddy contacts Let's Encrypt
↓
Let's Encrypt: "Prove you own example.com"
↓
Caddy: "I'll respond to a DNS or HTTP challenge"
↓
Challenge succeeds
↓
Let's Encrypt issues certificate
Certificates are stored securely:
# Typically in:
~/.local/share/caddy/certificates/
# Or as a service:
/root/.local/share/caddy/certificates/
Caddy manages file permissions and security automatically.
Caddy monitors certificate expiry and renews automatically:
Every 24 hours, Caddy checks:
↓
Certificate expires in < 30 days?
↓
YES → Request renewal from Let's Encrypt
NO → Continue serving
↓
Renewal succeeds
↓
No downtime, no restart needed
New certificates are deployed without downtime:
Old certificate serving traffic
↓
New certificate obtained
↓
Caddy switches to new certificate gracefully
↓
Connection stays alive
↓
Zero downtime update
| Platform | Installation | Status |
|---|---|---|
| Linux (apt) | sudo apt install caddy | ✅ Official |
| Linux (manual) | Download binary | ✅ Official |
| macOS (brew) | brew install caddy | ✅ Official |
| Windows | Download binary or chocolatey | ✅ Official |
| Docker | docker pull caddy | ✅ Official |
| Raspberry Pi | Compile or download | ✅ Works |
| Requirement | Minimum | Recommended |
|---|---|---|
| RAM | 256 MB | 512 MB |
| Storage | 50 MB | 100 MB |
| Bandwidth | 1 Mbps | 10 Mbps |
| CPU | 1 core | 2 cores |
Caddy is lightweight and runs almost everywhere.
Internet
↓
Nginx (port 80, 443)
↓
Certificate management (Certbot, cron)
↓
Node.js App (port 3000)
↓
Database
Problems:
Nginx doesn't auto-renew
Certbot renewal can fail
Separate tools to manage
More attack surface
Internet
↓
Caddy (port 80, 443)
- HTTPS automatic
- Certificates automatic
- Reverse proxy
↓
Node.js App (port 3000)
↓
Database
Advantages:
Everything integrated
Zero manual maintenance
Fewer moving parts
Simpler deployment
Running Node.js directly on ports 80/443 is:
| Issue | Impact |
|---|---|
| Requires root access | Security risk |
| App crashes = downtime | No process manager restart |
| Certificate management | Complex in code |
| Performance | App wastes cycles on HTTPS |
| Security | Too many responsibilities |
Caddy (handles HTTPS, certificates, security)
↓
Node.js (focuses on business logic)
This separation means:
✅ Node.js runs on port 3000 (no special access)
✅ Caddy handles HTTPS professionally
✅ Caddy and Node.js can restart independently
✅ Application code is cleaner
✅ Easier to scale (add more Node.js instances)
myblog.com {
reverse_proxy localhost:3000
}
Setup time: 5 minutes
api.example.com {
reverse_proxy localhost:3000
}
admin.example.com {
reverse_proxy localhost:3001
}
cdn.example.com {
file_server
root /var/www/cdn
}
Setup time: 15 minutes
example.com {
reverse_proxy localhost:3000 localhost:3001 localhost:3002 {
policy round_robin
}
}
Distribute load across multiple Node.js instances
example.com {
file_server
root /var/www/public
}
Serve static HTML, CSS, JS securely with HTTPS
localhost:443 {
file_server
}
Test HTTPS locally (useful for OAuth, webhooks, etc.)
ExpressJS + HTTPS:
const https = require('https');
const fs = require('fs');
const app = require('./app');
const options = {
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem')
};
https.createServer(options, app).listen(443);
Problems:
App must run as root (security risk)
Certificate renewal in code (complex)
No process restart without losing connections
Mixing concerns (HTTPS + business logic)
Caddy approach:
Node.js on port 3000 (no root)
Caddy handles HTTPS
Can restart Node.js independently
Clean separation
Heroku:
✅ HTTPS automatic (like Caddy)
✅ Deployment simple
❌ More expensive ($7/month minimum)
❌ Less control
❌ Vendor lock-in
Caddy (self-hosted):
✅ HTTPS automatic (like Caddy)
✅ Full control
✅ Cheaper (one-time VPS cost)
✅ No vendor lock-in
❌ Need to manage server
example.com {
reverse_proxy localhost:3000
}
No configuration needed. HTTPS is automatic and mandatory.
Prevents downgrade attacks:
Browser learns: "This site only uses HTTPS"
↓
If user types http://example.com
↓
Browser automatically upgrades to https://
Caddy enables this automatically.
Caddy can add security headers to all responses:
example.com {
header X-Frame-Options "DENY"
header X-Content-Type-Options "nosniff"
header X-XSS-Protection "1; mode=block"
reverse_proxy localhost:3000
}
Only supports modern TLS versions (1.2+):
✅ TLS 1.3 (latest, fastest)
✅ TLS 1.2 (widely supported)
❌ TLS 1.1, 1.0 (disabled for security)
Pin specific certificates to prevent man-in-the-middle attacks.
# 1. Install Caddy (one command)
sudo apt install caddy
# 2. Create configuration (one line)
echo 'example.com { reverse_proxy localhost:3000 }' | sudo tee /etc/caddy/Caddyfile
# 3. Start Caddy (one command)
sudo systemctl start caddy
# Done! Visit https://example.com
Your Node.js app (running on port 3000) is now publicly accessible with HTTPS, automatic certificates, and automatic renewal.
No Certbot. No cron jobs. No manual renewal. Just HTTPS.
Caddy is a modern web server designed for 2020s best practices, not legacy systems.
Automatic HTTPS is revolutionary - No more certificate management headaches.
Configuration is simple - Caddyfile is human-readable, unlike Nginx configs.
Perfect for Node.js - Separates concerns (Caddy handles HTTP, Node.js focuses on logic).
Secure by default - Modern TLS versions, security headers, and best practices built-in.
Zero maintenance - Set it once, and it works forever.
For most projects, Caddy is the better choice over Nginx (unless you have extreme scale or need).
Now that you understand Caddy:
Install Caddy on your VPS
Create a Caddyfile for your domain
Run your Node.js app on a local port
Point your domain to your VPS
Reload Caddy and watch HTTPS work automatically
Add security features (headers, rate limiting, IP filtering)
Monitor logs to ensure everything is working
The complete setup guide is coming next in this series.
False. Caddy has been around since 2015 and powers thousands of production applications. It's stable and battle-tested.
False. Caddy is written in Go (like Docker, Kubernetes) and is highly efficient. It handles millions of requests per second just fine.
False. For most projects, Caddy's performance is indistinguishable from Nginx. The difference only matters at extreme scale (millions of concurrent connections).
False. Caddy has an active community, extensive documentation, and commercial support available.
False. Caddy is open-source and free. Same as Nginx.
Let's Encrypt (free HTTPS certificates)
ACME Protocol (certificate automation)
This guide covered Caddy fundamentals. The next guides cover:
Setting up Caddy with Node.js - Complete deployment walkthrough
Caddy Security Features - Headers, filtering, authentication
Advanced Caddy Configuration - Multiple apps, load balancing, plugins
Production Deployment - PM2, monitoring, logging, maintenance
Each guide builds on the previous one, taking you from setup to production-grade deployment.

29 Dec 2025
Node.js vs Python: Which is Better for Back-End Development?

25 Dec 2025
Top 5 Animated UI Component Libraries for Frontend Developers

24 Dec 2025
Why Most Modern Apps use Kafka