I Switched from Nginx to Caddy and I'll Never Go Back

I used to be an Nginx fan. Had it running everywhere - personal projects, client sites, production servers. The config files made sense once you learned them, and it was rock solid.
But a few years ago, I discovered Caddy. The setup was so much simpler. Just a single binary. The config files were actually readable. And the automatic HTTPS just… worked.
I haven’t deployed Nginx since. These days, I’m building UserJot (a tool that helps teams collect and manage user feedback), and Caddy has been instrumental in keeping our infrastructure simple while we focus on the product.
The Moment It Clicked
This is a complete, production-ready reverse proxy configuration in Caddy:
api.myapp.com {
reverse_proxy localhost:3000
}
That’s it. Three lines. HTTPS included. Certificates auto-generated. HTTP/2 enabled.
When I first saw this, I didn’t believe it. Where’s the SSL configuration? The cipher suites? The certificate paths?
There aren’t any. Caddy just handles it.
Real-World Example: Node + TypeScript + Hono
Let me show you how simple this gets. Say you have a Hono API running on port 3000:
import { Hono } from 'hono';
const app = new Hono();
app.get('/api/health', (c) => {
return c.json({ status: 'ok', timestamp: new Date() });
});
app.post('/api/data', async (c) => {
const body = await c.req.json();
// Your business logic here
return c.json({ received: body });
});
export default app;
To put this behind Caddy with HTTPS, custom domain, and all the goodies:
- Install Caddy (one command)
- Create a Caddyfile:
api.yourapp.com {
reverse_proxy localhost:3000
}
- Run
caddy start
Done. Your API is now available at https://api.yourapp.com
with:
- Auto-provisioned Let’s Encrypt certificate
- Automatic HTTPS redirect
- HTTP/2 and HTTP/3 support
- Proper proxy headers
- Certificate auto-renewal
The Features That Made Me a Convert
Multiple Domains? Just Add More Blocks
Managing multiple domains in Caddy is refreshingly simple. Each domain gets its own block, and you can configure them independently. Need basic auth on your admin panel? Just add it to that block. Want different backends for different subdomains? No problem.
api.yourapp.com {
reverse_proxy localhost:3000
}
app.yourapp.com {
reverse_proxy localhost:3001
}
admin.yourapp.com {
reverse_proxy localhost:3002
basicauth {
admin $2a$14$Zkx19XL...
}
}
Each domain automatically gets its own SSL certificate. No manual cert management, no shared certificate hassles. Just clean, isolated configurations.
Need Load Balancing? One Line
Load balancing across multiple backend servers is built right in. Caddy will automatically distribute requests using round-robin by default, but you can choose other strategies like least connections or IP hash. The health checks ensure dead servers are automatically removed from the pool.
api.yourapp.com {
reverse_proxy localhost:3000 localhost:3001 localhost:3002 {
lb_policy round_robin
health_uri /health
health_interval 10s
}
}
This config spreads traffic across three backend servers, checks their health every 10 seconds, and automatically stops sending traffic to any server that fails the health check. No separate load balancer needed.
Want Wildcard Certificates? Easy
Need to handle arbitrary subdomains for your SaaS? Maybe customer1.yourapp.com, customer2.yourapp.com, etc? Wildcard certificates are just one line. Caddy will get a single wildcard cert from Let’s Encrypt and use it for all matching subdomains.
*.yourapp.com {
reverse_proxy localhost:3000
}
This is perfect for multi-tenant SaaS apps where each customer gets their own subdomain. The same config handles them all.
WebSocket Support? Already There
No special configuration needed. It just works. Caddy automatically detects WebSocket upgrade headers and handles them properly. No more hunting through docs for the right proxy headers.
The Dev Environment Setup That Actually Works
Here’s something useful: you can run the exact same Caddy setup locally with .local domains and HTTPS. No more localhost:3000
vs production URL mismatches.
First, add entries to your /etc/hosts
:
127.0.0.1 api.myapp.local
127.0.0.1 app.myapp.local
Then create a local Caddyfile:
api.myapp.local {
reverse_proxy localhost:3000
}
app.myapp.local {
reverse_proxy localhost:3001
}
Run Caddy with watch mode:
caddy run --watch
Now you’re developing with:
- Real HTTPS (Caddy generates local certs)
- Production-like URLs
- Automatic config reloading when you edit the Caddyfile
- The exact same setup as production, just with .local instead of .com
I run Caddy once when I start working, and it handles all my services in the background. Change the Caddyfile? Caddy reloads automatically. Need a new service? Add three lines, save, done.
This has been really helpful for testing webhooks, OAuth flows, and anything else that needs proper URLs and HTTPS in development.
The UserJot Backend Runs on This
At UserJot, our entire backend runs on Caddy. Yes, it gets more complex than these examples—we handle customer subdomains, dynamic routing, and API versioning. But this simple foundation scales up beautifully.
What starts as a 3-line config can grow into a sophisticated routing system without ever becoming the maintenance nightmare that Nginx configs tend to become.
The Honest Comparison
Nginx: Powerful, battle-tested, but configuration is complex. Every change feels risky.
Apache: Please, it’s 2024.
Traefik: Great for Kubernetes, overkill for everything else.
HAProxy: Amazing for load balancing, but HTTPS is still painful.
Caddy: Just works™. Seriously.
When Caddy Might NOT Be Right
I’ll be honest—Caddy isn’t perfect for everything:
- If you need ultra-fine-grained control over every aspect of request handling
- If you’re serving 100k+ requests per second (though v2 is surprisingly fast)
- If your org has deep Nginx expertise and custom modules
But for the other 95% of us? Caddy is much simpler.
Get Started in 5 Minutes
Option 1: Direct Install
# Ubuntu/Debian
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy
Option 2: Docker
FROM caddy:alpine
COPY Caddyfile /etc/caddy/Caddyfile
Your First Caddyfile
localhost:8080 {
respond "Hello, Caddy!"
}
Run caddy run
and visit http://localhost:8080. You’re now running Caddy.
The Challenge
Here’s my challenge to you: Take your simplest reverse proxy setup and try Caddy this week. Just one service. I bet you’ll be surprised at:
- How much simpler the config is
- How much faster you can make changes
- How much less you worry about SSL certificates
Final Thoughts
Switching to Caddy was one of those decisions that just keeps being useful. Every time I need to add a new domain or service, it’s a two-minute job instead of a two-hour debugging session.
If you’re building SaaS products where you need to move fast and focus on your actual product instead of infrastructure, Caddy makes sense. At UserJot, we’ve been able to ship features faster because we’re not constantly fighting with our reverse proxy config. When you’re building a feedback management tool that helps teams stay connected with their users, the last thing you want is to waste time on DevOps complexity. Caddy handles our customer subdomains, API routing, and widget endpoints without any drama.
If you’re curious about what we’re building, check out UserJot - we help SaaS teams collect feedback, manage roadmaps, and keep users in the loop with changelogs. And yes, it all runs on Caddy.
You might also like
I Stopped Using Try-Catch in TypeScript and You Should Too
Learn how to handle errors in TypeScript using the Result pattern instead of try-catch blocks.
How Do You Know If You Have Product-Market Fit?
The brutal truth about product-market fit: if you're asking whether you have it, you don't. Learn the real signs of PMF, why most startups get it wrong, and the two paths to finding it.
I Replaced MongoDB with a Single Postgres Table
Discover how Postgres and its JSONB data type can replace MongoDB for most NoSQL use cases. Get schema flexibility, ACID compliance, and fast queries from a single table.
Customer Retention Metrics for SaaS: The Complete Guide
Master SaaS retention metrics: Calculate churn rate, NRR, LTV, and 15+ KPIs. Get proven strategies, real benchmarks, and actionable frameworks to reduce churn by 50%.