Back to Blog
devops

How I Replaced Nginx Chaos with One Clean Caddyfile

November 4, 20258 min min read

Last week, I was deep in deployment hell. More than 10 apps. 10+ APIs. One overworked developer (me).

Every time I touched Nginx, something broke. Semicolons went missing, SSL expired, or ports collided. All I wanted was for report-api, report.app, and payments-demo.sksushil.info.np to just work.

Then I met Caddy — and my nights got quieter.

The Problem: Too Many Configs, Too Little Sanity

Each microservice meant another Nginx file. Another reload command. Another moment wondering, "Why isn't this proxying?"

And let's be honest: debugging Nginx inside Docker feels like fighting your own reflection.

I needed something that understood Docker — something clean, automatic, and smart.

The "Wait, That's It?" Moment

A friend told me:

Caddy is like Nginx, but with auto HTTPS and no pain.

I tried it. I wrote this one file called Caddyfile:

report-app.sksushil.info.np {
    reverse<em>proxy report-app:80
}</p><p>app1.sksushil.info.np {
    reverse</em>proxy app-1:80
}
app1-api.sksushil.info.np {
    reverse<em>proxy app1-api:8080
}
payments-demo.sksushil.info.np {
    reverse</em>proxy payments-app-demo:8080
}

One reload command later, everything was live. SSL worked. Routing worked. I didn't cry.

Note on SSL Conflicts (Cloudflare Users)

Sometimes Caddy's automatic HTTPS can conflict with Cloudflare Flexible SSL, causing redirect loops.

By default, Caddy tries to get a Let's Encrypt certificate. This can clash with Cloudflare's SSL.

Solution: Explicitly serve plain HTTP in Caddy:

http://payments-demo.sksushil.info.np {
    reverse<em>proxy payments-app-demo:8080
}

This tells Caddy: "Do not get certificates — just serve HTTP." Now Cloudflare handles SSL entirely.

SPAs in Caddy v2

For single-page apps (SPAs), use rewrite like this:

report-app.sksushil.info.np {
    reverse</em>proxy report-app:80
    @notFound {
        not file
    }
    rewrite @notFound /index.html
}

The Secret: Shared Docker Network

Caddy needs to see your app containers — like neighbors on the same street:

docker network create shared-net

Caddy Setup

services:
  caddy:
    image: caddy:latest
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
    networks:
      - shared-net

The Ending I Wanted

Now my production stack runs three sites, two APIs, and one proxy — all behind a single elegant Caddyfile. No /etc/nginx/sites-enabled, no renewal scripts, no fragile reloads.

Just this:

docker exec -it caddy caddy reload --config /etc/caddy/Caddyfile

…and a peaceful developer sipping coffee while SSL renews itself.

Share this article

Tags

Caddy Nginx Docker DevOps

Subscribe to Newsletter

Get notified about new articles