Caddy is Amazing

When I first discovered Caddy, a web server and reverse proxy written in Go, it had only recently reached its first stable release and the community was still quite small. At the time, I didn’t think much of it and simply saw it as another interesting project built in Go. I was working at FNU CLTE, where we used Nginx as our reverse proxy and it served its purpose. It was mature, widely used, and performant. Although, a few years later, we switched to Traefik since it worked better with Docker Swarm, which we were using for our deployments.

Recently, while setting up a new server for some workloads at my part-time job, I decided to revisit Caddy as an option for the reverse proxy role. The setup process was pretty simple. Within minutes, I had a working configuration that handled HTTPS automatically, redirected HTTP to HTTPS, and served multiple sites with almost no boilerplate. The configuration file itself felt intuitive and easy to understand at a glance. In this post, I just wanted to share my experience with Caddy and how it compares to Nginx in aspects that are important to me.

Automatic HTTPS

Caddy’s automatic HTTPS is easily one of the biggest selling points for me. You don’t have to manually obtain or renew certificates or configure certbot separately from your reverse proxy setup. Just declare your domain in the Caddyfile and Caddy takes care of obtaining, renewing, and serving the certificates automatically. With Nginx, I’d always have Certbot setup for obtaining and renewing SSL certificates, and to automatically reload Nginx after a renewal. With Caddy, that entire category of “certificate-related problems” just do not exist.

Take a look at how simple configuring SSL certificate in Caddyfile really is:

{
    # optional, but recommended
	email john.doe@example.com
}

example.com {
    # configuration for example.com
}

That’s it. No extra directives, no extra cron job or hook setup outside Caddy, no more prompting LLMs to figure out how to setup SSL. Caddy will look at example.com domain and take care of everything for you. Pretty neat huh? Let’s see how this compares with Nginx in the next section.

Automatic HTTPS Redirection

Caddy’s automatic HTTPS doesn’t just handle obtaining and renewing certificates. It also takes care of HTTP to HTTPS redirection automatically. There’s no need for extra configuration. Suppose you’re setting up a simple file server. Here’s what that looks like in Caddy versus Nginx.

Caddy:

example.com {
    root * /var/www/site
    file_server
}

Voila! Caddy automatically listens on both HTTP and HTTPS, redirects traffic to HTTPS, and manages certificates behind the scenes. You don’t have to touch Certbot, define redirect blocks, or remember renewal hooks. Now, compare the following.

Nginx:

# HTTP virtual host for challenge + redirect
server {
    listen 80;
    server_name example.com;

    # ACME HTTP-01 challenge location
    location ^~ /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;
    }

    # redirect everything else to HTTPS
    location / {
        return 301 https://$host$request_uri;
    }
}

# HTTPS virtual host after certs exist
server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    root /var/www/site;
    index index.html;
}

With Nginx, you also need to install and configure Certbot (or another ACME client) to obtain and renew certificates, plus set up a hook or scheduled job to reload Nginx after each renewal.

For someone who isn’t an Nginx expert, this setup can be tedious and it’s easy to forget the moving parts when you revisit it months later. The example above covers just one domain; imagine maintaining multiple domains with the same pattern. That’s a lot of boilerplate for something Caddy handles automatically with only a few lines.

Convenient Built-in Primitives

Another thing I love about Caddy is how many useful features come built-in. You don’t need to install extra modules, configure sidecar tools, or chain together third-party scripts to get common web-server behaviour. Caddy ships with practical primitives that just work, and most of the time, they work exactly the way you expect them to.

Reverse Proxying

Caddy’s reverse proxy support is one of its strongest built-ins and I absolutely love it. You can set up an HTTPS-terminating reverse proxy in seconds.

example.com {
    reverse_proxy localhost:3000
}

This single directive will automatically wire up full production-grade reverse proxy pipeline with sensible defaults and automatic behaviours. For instance, Caddy automatically sets the standard proxy headers (Host, X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host) before passing the request to your backend. It also performs load balancing if you list multiple backends, handles retries, detect unhealthy upstreams, and so on.

server {
    listen 80;
    server_name example.com;

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

With Nginx, you need to manually define every forwarding header, handle SSL separately, and configure upstreams through a separate block if you want load balancing. It’s powerful, but not exactly beginner-friendly.

Static File Service with Compression

This one doesn’t make too much of a difference for me, but I still wanted to mention it. Caddy has sensible defaults that allow you to avoid thinking about directory indexes, MIME types, or compression settings. Compare the following:

Caddy:

example.com {
    root * /var/www/site
    encode zstd gzip
    file_server
}

That’s all you need to serve your site with compression enabled.

Nginx:

server {
    listen 80;
    server_name example.com;

    root /var/www/site;
    index index.html;

    gzip on;
    gzip_types text/plain text/css application/javascript application/json image/svg+xml;
    gzip_min_length 1024;
}

Here, enabling compression requires multiple directives. If you want Brotli support, you’ll need to install or build an additional module. Caddy gives you both gzip and zstd compression right out of the box. No compilation flags, no guess work of which MIME types to include.

This Isn’t All

I’ve only listed the ones that are important to me at the moment. There are tons of other powerful features of Caddy that I haven’t fully explored. Things like automatic OCSP stapling, on-the-fly configuration reloads without dropping connections, first-class HTTP/3 support, and JSON API for dynamic configuration, growing ecosystem of official and community extensions like security modules, authentication helpers, rate-limiting plugins, integration with DNS providers, all contribute to the feeling that Caddy is designed for the modern day.

Final Thoughts

For most modern use cases, Caddy feels like a breath of fresh air. It’s simple, elegant, and clearly built to address the pain points of older tools like Nginx. After having some hands-on experience, I can confidently say that if I were setting up a new project today, I’d reach for Caddy before Nginx almost every time.