Skip to content

fix(openresty): correct gzip defaults and add http.d for managed http directives - #9230

Draft
Snrat wants to merge 2 commits into
1Panel-dev:devfrom
Snrat:feat/gzip-brotli-defaults
Draft

fix(openresty): correct gzip defaults and add http.d for managed http directives#9230
Snrat wants to merge 2 commits into
1Panel-dev:devfrom
Snrat:feat/gzip-brotli-defaults

Conversation

@Snrat

@Snrat Snrat commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Body

Applies to 1.31.1.1-2-4-noble only. Older versions ship neither
module.catalog.json nor Dockerfile.modules, so nginxModuleDynamicSupported
returns false for them and they cannot use dynamic modules at all.

Companion PR: 1Panel-dev/1Panel#13639. This one must be merged first
without the include added here, the files the panel writes to conf/http.d
are never loaded. The panel probes for the directory and stays inert when it is
missing, so merging out of order degrades silently rather than breaking.

1. gzip defaults

These values have been unchanged since 1.21.4.3 and no longer match how sites
are served today.

Directive Before After Why
gzip_types no application/json +json, ld+json, text/xml, xhtml+xml, rss+xml, atom+xml, wasm, svg+xml, ttf, otf JSON API responses were served uncompressed. For an API-backed site this is the single largest loss in the whole file.
gzip_comp_level 2 5 Level 2 gives up roughly 8–15% ratio on text; 5 sits at the cost/ratio knee.
gzip_proxied expired no-cache no-store private auth any The old value only compressed proxied responses carrying those specific Cache-Control semantics. A common upstream response such as Cache-Control: public, max-age=3600 matched no condition and was never compressed — this affected reverse-proxy sites, one of the main use cases.
gzip_disable "MSIE [1-6]\." removed A per-request User-Agent regex for browsers with no measurable share.

Already compressed formats (images other than SVG, woff/woff2, archives, media)
are deliberately excluded: recompressing them costs CPU and usually grows the
payload.

gzip_static is intentionally not enabled. nginx does not verify that a
.gz file is newer than its source, so a stale artifact would be served
indefinitely with no error. The module stays compiled in and can be enabled
per site by users who generate .gz at build time.

2. conf/http.d

A directory for http-context directives that 1Panel generates and owns,
mounted read-only into the container.

conf/modules-enabled cannot serve this purpose: load_module is a
main-context directive, so that directory is included at the top level of
nginx.conf and cannot host http-context directives such as brotli on.

The include is emitted before conf.d/*.conf so per-site configuration
keeps overriding these panel-managed globals.

upgrade.sh injects the include into existing installations, mirroring the
handling already in place for the modules-enabled include. It is idempotent
and degrades to a warning when the conf.d include is absent, which happens
when a user has heavily customised nginx.conf.

Testing

Verified against the real image with a real ngx_brotli .so, compiled using
this repo's own Dockerfile.modules.

  • Fresh install: init.sh creates conf/http.d; nginx -t passes; include
    ordering confirmed (http.d at line 48, conf.d at 49).
  • Upgrade from the pre-change layout: include injected at the right place with
    matching indentation; idempotent across three consecutive runs; nginx -t
    passes; every unrelated line preserved.
  • Measured on a 16 KB JSON response: 16791 B → 1093 B (−93.5%).
    HTML page: 9042 B → 146 B.

38 assertions across the fresh-install and upgrade suites, all passing.

Snrat added 2 commits August 26, 2026 04:12
Introduce `conf/http.d/`, a directory reserved for http-context directives
that 1Panel generates and owns, mounted read-only into the container.

`conf/modules-enabled/` cannot serve this purpose: it is included at the top
level of nginx.conf because `load_module` is a main-context directive, so
http-context directives such as `brotli on` cannot be placed there.

The include is emitted before `conf.d/*.conf` so that per-site configuration
continues to take precedence over panel-managed global defaults.

`upgrade.sh` injects the include into existing installations, mirroring the
handling already in place for the modules-enabled include. It is idempotent
and degrades to a warning when the conf.d include is absent, which happens
when a user has heavily customised nginx.conf.
These defaults have been unchanged since 1.21.4.3 and no longer match how
sites are actually served.

gzip_types was missing application/json, so JSON API responses were served
uncompressed. For an API-backed site this is the single largest loss in the
whole configuration. Also add ld+json, text/xml, xhtml+xml, rss+xml,
atom+xml, wasm, svg+xml and ttf/otf, all of which compress well. Already
compressed formats (images, woff2, archives) are deliberately excluded.

gzip_comp_level 2 -> 5. Level 2 gives up roughly 8-15% ratio on text
compared to level 5, which sits at the cost/ratio knee for gzip.

gzip_proxied expired|no-cache|no-store|private|auth -> any. The previous
value only compressed proxied responses carrying those specific
Cache-Control semantics, so a common upstream response such as
`Cache-Control: public, max-age=3600` matched no condition and was never
compressed. This affected reverse-proxy sites, one of the main use cases.

Drop gzip_disable "MSIE [1-6]\." — a per-request User-Agent regex for
browsers with no measurable traffic share.

gzip_static is intentionally not enabled: nginx does not verify that a
`.gz` file is newer than its source, so a stale precompressed file would be
served indefinitely with no error. The module stays compiled in and can be
enabled per site by users who generate `.gz` artifacts at build time.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant