Originally published on Naveed Ahmed's Tech Blog.
If you've worked with Nginx or Apache, you've probably spent some time configuring virtual hosts, setting up SSL certificates, troubleshooting reverse proxy rules, or fixing certificate renewal issues.
These are normal parts of managing web infrastructure, but they can become repetitive, especially when you're responsible for multiple services.
That's where Caddy caught my attention.
Caddy is an open-source web server and reverse proxy written in Go. One of its biggest advantages is that it handles HTTPS automatically, including obtaining and renewing TLS certificates.
You can also configure a reverse proxy with just a few lines.
But how does Caddy actually work? Is it secure enough for production? And should you consider it instead of Nginx or Apache?
I put together a detailed guide to explore these questions.
Here's a practical overview of what I covered.
1. What Is Caddy, and Why Should DevOps Engineers Care?
Caddy is a modern web server that can serve websites, handle HTTPS connections, and route incoming requests to backend applications.
It supports many features you'd expect from a production web server:
- Automatic HTTPS and TLS certificate renewal
- Reverse proxy and load balancing
- HTTP/2 and HTTP/3 support
- Simple Caddyfile configuration
- Dynamic configuration through a REST API
- Docker and container-based deployments
What makes Caddy interesting isn't that these features are impossible to implement with other web servers.
It's that several of them work with very little configuration.
For example, here's a basic reverse proxy configuration:
example.com {
reverse_proxy localhost:3000
}
That's enough to tell Caddy to forward requests to an application listening on port 3000.
With a public domain, correct DNS records, and the required network access, Caddy can also provision and manage HTTPS certificates automatically.
For small applications and containerized services, this can save quite a bit of setup time.
2. Caddy vs. Nginx vs. Apache: Which One Makes Sense?
Nginx and Apache have been around for years, and there's a reason they're still widely used.
Both are mature, flexible, and proven in production environments.
Caddy takes a slightly different approach, with more emphasis on sensible defaults and simplifying common configuration tasks.
Here's how I look at the three:
| Feature | Caddy | Nginx | Apache |
|---|---|---|---|
| Automatic HTTPS | Built-in | Usually requires additional setup | Usually requires additional setup |
| Configuration | Simple Caddyfile | Directive-based | Directive-based |
| Reverse proxy | Built-in | Built-in | Available through modules |
| HTTP/3 | Supported | Supported in compatible builds | Depends on implementation |
| Dynamic configuration | REST API | Depends on edition and tooling | Typically file-based |
| Common use case | Simple HTTPS and reverse proxy setups | High-performance web serving and proxying | Flexible web hosting |
I wouldn't say Caddy is automatically better than Nginx or Apache.
If you already have a well-managed Nginx environment, there's no reason to migrate just for the sake of using something newer.
But if you're starting a new project and want HTTPS and reverse proxy configuration to be straightforward, Caddy is worth considering.
The best choice still depends on your workload, operational requirements, and existing infrastructure.
3. How Secure Is Caddy?
Security is one of the more interesting parts of Caddy's architecture.
Because it's written in Go, Caddy benefits from memory safety features that help prevent certain classes of memory-related vulnerabilities common in manually managed memory environments.
However, that doesn't mean Caddy is free from vulnerabilities.
Like any web server, it still needs proper configuration, timely security updates, and operational monitoring.
For production environments, I'd pay particular attention to:
- Running the service with minimal privileges
- Restricting access to the administration API
- Keeping Caddy and its modules updated
- Protecting TLS certificate storage
- Limiting unnecessary network exposure
- Monitoring logs and reverse proxy errors
One important detail is Caddy's administration API, which normally listens on a local interface.
It's useful for dynamic configuration, but you shouldn't expose it publicly without appropriate security controls.
Automatic HTTPS is a helpful feature, but it doesn't replace good server security practices.
4. Installing Caddy on Ubuntu or Debian
Getting started is relatively simple.
For Ubuntu and Debian systems, Caddy provides an official package repository.
First, install the required packages:
sudo apt install -y \
debian-keyring \
debian-archive-keyring \
apt-transport-https \
curl \
gnupg
Add the official repository signing key:
curl -1sLf \
'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
| sudo gpg --dearmor \
-o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
Add the repository:
curl -1sLf \
'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
| sudo tee /etc/apt/sources.list.d/caddy-stable.list
Make the repository files readable:
sudo chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg
sudo chmod o+r /etc/apt/sources.list.d/caddy-stable.list
Install Caddy:
sudo apt update
sudo apt install -y caddy
Verify the installation:
caddy version
sudo systemctl status caddy --no-pager
The official package installs Caddy as a systemd service, making it easier to manage on Linux servers.
5. Using Caddy as a Reverse Proxy
Let's say you're running a Node.js application on port 3000.
Instead of exposing that application directly to the internet, you can put Caddy in front of it.
A simple Caddyfile looks like this:
app.example.com {
encode zstd gzip
reverse_proxy 127.0.0.1:3000
}
In this example, Caddy:
- Receives requests for
app.example.com - Handles HTTPS automatically when the domain and network are configured correctly
- Enables response compression
- Forwards requests to the Node.js application
Before applying changes, validate the configuration:
sudo caddy validate --config /etc/caddy/Caddyfile
Then reload Caddy:
sudo systemctl reload caddy
This is one of my favorite parts of Caddy.
A common reverse proxy setup doesn't need a large configuration file, and you don't have to manage a separate certificate renewal workflow for typical publicly accessible domains.
6. Running Caddy with Docker
If you use Docker regularly, Caddy can also run as a container.
For example, you can start it with:
docker run -d \
--name caddy \
-p 80:80 \
-p 443:443 \
-p 443:443/udp \
-v "$PWD/Caddyfile:/etc/caddy/Caddyfile:ro" \
-v caddy_data:/data \
-v caddy_config:/config \
caddy:2
This assumes you've created a Caddyfile in your current directory.
The named volumes are important because Caddy needs persistent storage for certificate data and configuration state.
For a real deployment, I'd recommend using Docker Compose and placing Caddy on the same Docker network as the backend services.
That way, you can route requests using container service names rather than exposing every application's ports publicly.
For example:
app.example.com {
reverse_proxy app:3000
}
Here, app would be the name of a backend service reachable through the shared Docker network.
It's a clean approach for applications that need HTTPS without a complicated reverse proxy setup.
7. Would I Use Caddy in Production?
Yes, I'd consider it, particularly for environments where keeping the web server simple is a priority.
Some good use cases include:
- Small and medium-sized web applications
- Containerized services
- Internal applications and developer platforms
- Reverse proxies for APIs
- Self-hosted tools and dashboards
But there are still things I'd check before deploying it:
- Certificate storage: Make sure certificate data survives container restarts and redeployments.
- Networking: Confirm DNS records, firewall rules, and ports 80 and 443 are configured correctly.
- Monitoring: Collect access logs, error logs, and relevant operational metrics.
- Security: Keep the admin API restricted and run the service with appropriate permissions.
- Configuration management: Store the Caddyfile in version control and validate changes before deployment.
And, as with any infrastructure component, I'd test it against the actual application's requirements rather than choosing it based on benchmark numbers alone.
π Want to Go Deeper?
This post covers the basics, but there's more to consider when running Caddy in production.
I wrote a longer, hands-on guide that explores:
- Caddy's architecture and request handling
- Caddy vs. Nginx vs. Apache
- Security considerations and hardening
- Installing Caddy across different environments
- Docker deployment and reverse proxy configuration
- Automatic HTTPS and certificate management
- Production configuration and troubleshooting
If you're evaluating Caddy for your next project, the complete tutorial should help you get started.
π Read the Complete Caddy Web Server Tutorial: Architecture, Docker & Production Guide
Final Thoughts
One thing I've learned while working with infrastructure is that more configuration doesn't necessarily mean a better setup.
Sometimes, reducing the number of moving parts makes systems easier to maintain.
That's what makes Caddy interesting to me.
It doesn't replace every use case for Nginx or Apache, but it offers a practical alternative, especially when automated HTTPS and simple reverse proxy configurations are priorities.
I'd be interested to hear how others are using it.
Have you tried Caddy in production? Do you prefer it over Nginx, Apache, or Traefik?
Share your experience in the comments. I'd love to hear what worked, what didn't, and whether you'd recommend it.
About the Author
I'm Naveed Ahmed, a Lead DevOps & Platform Architect. I write about DevOps, Linux, cloud infrastructure, containers, Kubernetes, and production engineering.
You can find more of my work here:
- π Personal Website
- βοΈ Technical Blog
- πΌ LinkedIn
- π GitHub
- π οΈ 970+ SRE Interview Scenarios
- βΈοΈ Kubernetes Learning Resources
- π¬ DevOps Dispatch Newsletter
Thanks for reading, and happy building!
Top comments (0)