# Caddy in front of app’s Nginx via Docker Compose

**URL:** https://meta.discourse.org/t/caddy-in-front-of-app-s-nginx-via-docker-compose/410123
**Category:** Self-hosting
**Tags:** nginx, install
**Created:** [15 augustus 2026 om 07:03 UTC](https://meta.discourse.org/t/caddy-in-front-of-app-s-nginx-via-docker-compose/410123 "2026-08-15T07:03:16Z")
**Posts on this page:** 6
**Page:** 1

<div class="post-metadata">

### Author: ![Ethsim2](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/ethsim2/32/522255_2.png) [@Ethsim2](https://meta.discourse.org/u/Ethsim2)
#### Post date: [15 augustus 2026 om 07:03 UTC](https://meta.discourse.org/t/caddy-in-front-of-app-s-nginx-via-docker-compose/410123/1 "2026-08-15T07:03:16Z")

</div>

I’ve upgraded one of my Discourse forum servers, so to open UDP 443 rather than only TCP in the IONOS Cloud Panel firewall (i’m from GB).

I don’t use CDN because this particular forum isn’t big user number, but has lots of categories.

I had some difficulty in ensuring the IP geos through `nginx` correctly, and the forum performs well now with no issues in `/logs`.

I have rotating proxy logs set up with Docker Compose and appreciate that Caddy does some automatic redaction of sensitive field in the proxy logs, something i haven’t quite noticed with Nginx before. Though i only recall using proxy logs twice in the 2 years i’ve used Discourse in production.

I’m hoping to gather how much interest there would be if i published a guide going through what commands i ran, and what happened when i ran them, to get from the [Beginner docker install guide](https://github.com/discourse/discourse/blob/main/docs/INSTALL-cloud.md) to the slightly more mobile-friendly forum now - i can include commands for future maintenance, i.e. upgrading Caddy to a newer release, but i haven’t tested these commands on my IONOS L VPS yet.

---

<div class="post-metadata">

### Author: ![pfaffman](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/pfaffman/32/120154_2.png) [@pfaffman](https://meta.discourse.org/u/pfaffman)
#### Post date: [15 augustus 2026 om 17:37 UTC](https://meta.discourse.org/t/caddy-in-front-of-app-s-nginx-via-docker-compose/410123/2 "2026-08-15T17:37:14Z")

</div>

FWIW, there’s this PR: [Add Caddy web server template as nginx alternative - Pull Request #952 - discourse/discourse\_docker - GitHub](https://github.com/discourse/discourse_docker/pull/952)

---

<div class="post-metadata">

### Author: ![Ethsim2](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/ethsim2/32/522255_2.png) [@Ethsim2](https://meta.discourse.org/u/Ethsim2)
#### Post date: [15 augustus 2026 om 20:50 UTC](https://meta.discourse.org/t/caddy-in-front-of-app-s-nginx-via-docker-compose/410123/3 "2026-08-15T20:50:52Z")

</div>

Thanks, I hadn’t seen that PR. That’s interesting - my setup is slightly different in that I’ve kept Discourse’s nginx web server in place and run Caddy separately in Docker Compose in front of it, mainly to provide `HTTP/3` at the edge.

There is one small Discourse-side change: I added a persistent `app.yml` hook so nginx uses Caddy’s `X-Forwarded-For` value for the real client IP when requests arrive over the Unix socket. I originally needed a Caddy `X-Real-IP` workaround, but after adding the nginx-side fix I was able to remove that and verify that Discourse still records the correct client IP.

I’ve also explicitly disabled `QUIC 0-RTT`/early data in the Caddy configuration while leaving HTTP/3 enabled, to avoid that additional early-data edge case.

My approach also enables Caddy’s HTTP access logging at the site level, which gives me Caddy’s default redaction of sensitive credential headers. I rotate the resulting Docker `json-file` logs at `25 MB × 3`. I noticed the PR currently has its rotating log block in Caddy’s global options, which Caddy’s documentation describes as configuring runtime logging rather than HTTP access logging.

So it isn’t quite a completely untouched [standard install](https://meta.discourse.org/t/142537?silent=true), but it’s also a different approach from replacing nginx with Caddy altogether.

I’ll have a closer look at that PR. For now I’m mainly interested in whether there is enough interest in this approach to make a step-by-step guide worthwhile, rather than starting on one immediately.

---

<div class="post-metadata">

### Author: ![Ethsim2](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/ethsim2/32/522255_2.png) [@Ethsim2](https://meta.discourse.org/u/Ethsim2)
#### Post date: [16 augustus 2026 om 07:47 UTC](https://meta.discourse.org/t/caddy-in-front-of-app-s-nginx-via-docker-compose/410123/4 "2026-08-16T07:47:10Z")

</div>

### Just a correction/clarification to what I said above.

When I originally described the result as a:

> [@Ethsim2](#):
>
> slightly

more mobile-friendly forum, part of what I had noticed was that the initial round trip when opening the iOS Safari PWA had previously felt too slow.

* * *

However, I now regret including this statement in my later reply:

> [@Ethsim2](#):
>
> I’ve also explicitly disabled QUIC 0-RTT/early data in the Caddy configuration while leaving HTTP/3 enabled, to avoid that additional early-data edge case.

Disabling `0-RTT` was something I tried while diagnosing the client-IP problem, but it does not appear to have been necessary. Both of the PostgreSQL errors involving `unix:` continued while `0rtt off` was already configured.

The change that appears to have resolved those errors was instead the persistent `app.yml` hook which modifies nginx so that it uses Caddy’s `X-Forwarded-For` value for the real client IP when requests arrive over the Unix socket.

I have therefore now removed the `0rtt off` setting and restored Caddy’s default behaviour. HTTP/3 is still working and the correct client IP is continuing to pass through to Discourse.

So if there is eventually enough interest for me to put together the step-by-step guide, I would **not** include disabling `QUIC 0-RTT` as a required part of the configuration.

---

<div class="post-metadata">

### Author: ![satonotdead](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/satonotdead/32/447830_2.png) [@satonotdead](https://meta.discourse.org/u/satonotdead)
#### Post date: [16 augustus 2026 om 12:07 UTC](https://meta.discourse.org/t/caddy-in-front-of-app-s-nginx-via-docker-compose/410123/5 "2026-08-16T12:07:11Z")

</div>

> [@Ethsim2](#):
>
> mainly to provide `HTTP/3` at the edge.

You can also do it from Vanilla Nginx: [https://quic.nginx.org/](https://quic.nginx.org/)

---

<div class="post-metadata">

### Author: ![Ethsim2](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/ethsim2/32/522255_2.png) [@Ethsim2](https://meta.discourse.org/u/Ethsim2)
#### Post date: [17 augustus 2026 om 07:20 UTC](https://meta.discourse.org/t/caddy-in-front-of-app-s-nginx-via-docker-compose/410123/6 "2026-08-17T07:20:40Z")

</div>

Thanks - I checked the nginx currently shipped inside my Discourse `app` container, and you’re right that it can provide HTTP/3 directly:

```plaintext
nginx 1.26.3-3+deb13u7
OpenSSL 3.5.6
--with-http_v3_module

```

So direct HTTP/3 from Discourse’s existing nginx is definitely a genuine alternative.

One reason I may still prefer Caddy for this particular setup is `0-RTT`, though. After my correction above I re-enabled Caddy’s default QUIC `0-RTT` behaviour, and on the iOS Safari PWA I noticed a significant improvement in the load experience I was trying to improve.

As far as I can tell from nginx’s documentation, nginx versions before `1.29.1` cannot enable `0-RTT` when built with OpenSSL, regardless of `ssl_early_data`, so the nginx `1.26.3` currently in my Discourse container can do HTTP/3 but not that particular optimisation.

So I’m interested in comparing the two approaches rather than assuming Caddy is necessary just for HTTP/3.
