Nginx crash-loops after container restart: base image ships brotli_static in discourse.conf but never loads the module (unknown directive)

Summary
After a routine container restart (host reboot / docker restart), nginx fails to start inside the app container with:

nginx: [emerg] unknown directive "brotli_static" in /etc/nginx/conf.d/discourse.conf:172

This puts the whole site down (no fallback — nginx never binds to 80/443) until manually patched.

Environment

  • Base image: discourse/base:2.0.20260812-0036
  • nginx: 1.26.3-3+deb13u7 (Debian 13/trixie package, not custom-compiled)
  • libnginx-mod-http-brotli-static/-filter 1.0.0~rc-6 are installed, and /etc/nginx/mods-enabled/*.conf symlinks (with correct load_module lines) exist and point to valid .so files.

Root cause
/etc/nginx/nginx.conf (owned by the nginx-common package, unmodified) has no include /etc/nginx/modules-enabled/*.conf; directive, so the installed Brotli modules are never loaded — while discourse.conf (generated by the Discourse templates) still emits brotli_static on; assuming they are.

Why it doesn’t fail immediately
The config only gets re-parsed when nginx actually (re)starts. A freshly rebuilt container can run fine for days/weeks until something restarts nginx (host reboot, docker restart, OOM, etc.), at which point it silently starts crash-looping (~1×/sec) with no automatic recovery.

Workaround
Add include /etc/nginx/modules-enabled/*.conf; as the first line of /etc/nginx/nginx.conf (main context, before events {}). Confirmed this restores nginx -t success and normal operation.

Ask
Could the base image either (a) add this include to its nginx.conf, or (b) drop brotli_static/brotli from the shipped discourse.conf template if the distro nginx package build no longer bundles Brotli support by default?

2 Likes

Correcting my own report: the base image is fine. The cause was local to our installation.

What actually happened

A local cron job on our host copies outdated, manually maintained nginx config files into the running container. One of them predates include /etc/nginx/modules-enabled/*.conf;, another still sets brotli_static on;. Together they produce unknown directive "brotli_static", and nginx refuses to start.

Why I misdiagnosed it

I inspected /etc/nginx/nginx.conf inside the running container and found the modules-enabled include missing. That file had already been overwritten locally, so the running container never reflected what Discourse actually generates. Checking the built image instead makes it obvious. The include is there:

docker create --name check local_discourse/app
docker cp check:/etc/nginx/nginx.conf ./
docker rm check

Why it surfaced with a delay

This may be the useful part for others. The job ends with nginx -s reload and discards its output. When the config is broken, the reload fails silently and the running nginx keeps serving from its already loaded config. Everything looks healthy. The breakage only becomes visible at the next real nginx start, which for us was an unattended kernel upgrade rebooting the host days later.

So if a Discourse container has been serving traffic fine and then dies on an unrelated reboot, check whether something overwrote its nginx config in the meantime.

Apologies for the noise, and thanks to anyone who spent time looking at this.

1 Like