Skip to content

Domitian

Latest

Choose a tag to compare

@lewisgoddard lewisgoddard released this 10 Sep 03:26
1f70142

Bubbly 3.0 is a rebuild rather than a refresh. The TLS configuration is re-sourced from the TLSRef configurator, the site templates are renamed and restructured around a default server, certificates are now ECDSA, and the whole repository is tested by CI on every push.

Upgrade Warnings

  • Ubuntu 26.04 LTS or Debian 13 only. 3.x needs Nginx 1.25.1+ for the http2 directive and OpenSSL 3.5+ for the X25519MLKEM768 group. Ubuntu 24.04, Ubuntu 22.04 and Debian 12 fail both. Stay on 2.2.0 on those. Check with nginx -V.
  • Site templates are renamed. bubbly_verify.conf is now bubbly_http.conf, bubbly_live.conf is now bubbly_https.conf. Rename your copies to match, or keep the old names and re-read them against the new ones.
  • The HTTP site now stays enabled permanently. It serves ACME challenges and redirects everything else, so renewals keep working after a certificate expires. Do not unlink example.com_http.conf after issuance.
  • There is a new install step: enable the default server. sites-available/bubbly_default.conf answers unmatched Host headers and connections without SNI, and — because a handshake begins before SNI selects a server — it is where ssl_protocols and ssl_ecdh_curve actually take effect for the whole socket. Run sudo rm -f /etc/nginx/sites-enabled/default first, or Nginx refuses to start with a duplicate default server.
  • The TLS profile moved. Protocols, groups and ciphers now live in directive/bubbly_ssl-profile.conf, included from server blocks; session resumption lives in conf.d/bubbly_ssl.conf, which Nginx loads by itself. bubbly_rock-hard-ssl.conf is now a thin per-server file. Never include the profile at the http level — Debian and Ubuntu set those directives in their own nginx.conf and a repeat is a fatal "directive is duplicate".
  • Diffie-Hellman parameters are gone. No DHE ciphersuites remain, so dhparam3.pem is unused. bubbly_generate-statics.sh has been removed; drop the ssl_dhparam line from any site file of your own.
  • The ACME webroot moved from /tmp/bubbly-authenticator to /var/lib/bubbly-authenticator. Any local user or process — a compromised PHP application included — can create a directory in /tmp before root does and then answer ACME challenges for any name pointing at the machine. Re-run bubbly_copy-configs.sh and reload before the next issuance.
  • Certificates are now ECDSA P-384 rather than RSA 3072. The next bubbly_renew-ssl.sh run replaces the key type. Anything pinning the key or expecting RSA needs updating.
  • Renewal is Certbot's systemd timer, not yours. The script now registers --deploy-hook "service nginx reload", recorded in /etc/letsencrypt/renewal/. Remove any Bubbly renewal cron job; crontab.conf has been deleted.
  • Session ticket keys ship commented out. Nginx 1.23.2+ generates and rotates its own in the shared cache. bubbly_generate-tickets.sh is now optional, for several instances behind a load balancer only, and requires uncommenting ssl_session_ticket_key in conf.d/bubbly_ssl.conf.
  • OCSP stapling ships off, in its own directive/bubbly_ocsp-stapling.conf. Let's Encrypt stopped issuing an OCSP responder URL on 2025-05-07 and shut the responders down on 2025-08-06, so ssl_stapling on; only logs a warning. Still worth enabling behind a CA that publishes one.
  • Expect-CT is gone, deprecated and ignored by every browser. X-XSS-Protection is now 0, the XSS Auditor having been removed as itself exploitable. The unprefixed Frame-Options, Content-Type-Options, XSS-Protection and X-UA-Compatible headers never existed and have been dropped, as has Access-Control-Allow-Origin: origin, which is not a valid value — same-origin is now expressed by sending no header.
  • server_tokens off; is now active rather than commented.
  • HSTS max-age is now 63072000 (two years), still without includeSubDomains.
  • Rate limit zones have been renamed and trimmed in conf.d/bubbly_limits.conf. bubbly_limits_20.conf and bubbly_limits_server_2k.conf are gone. Update any include of your own.
  • Nginx Amplify and NixStats stub-status configs have been removed.

Security

  • Re-source the TLS profile from the TLSRef configurator, Guideline v6.0, for Nginx 1.28 with OpenSSL 3.5.
  • Add X25519MLKEM768 as the preferred key exchange group, for post-quantum forward secrecy.
  • Issue ECDSA secp384r1 certificates instead of RSA 3072.
  • Move the ACME webroot out of /tmp, where it could be pre-created by any local user.
  • Add Cross-Origin-Embedder-Policy, Cross-Origin-Opener-Policy and Cross-Origin-Resource-Policy, each with three documented options.
  • Add Permissions-Policy, denying 21 features by default.
  • Raise HSTS max-age from one year to two.
  • Enable server_tokens off; by default.
  • Write ticket keys 600, and rotate rather than overwrite, so issued tickets keep working.
  • Add a default server, so an unknown Host or a connection without SNI is refused rather than being served whichever site sorts first.

Bugfix

  • bubbly_copy-configs.sh resolves its own directory, so it works from any working directory rather than only from ~.
  • The Content Security Policy is built across several set directives and emitted as one header. The previous multi-line quoted value produced a header containing literal newlines, which is invalid HTTP — and it was sent under a header name with a trailing colon.
  • expires no longer applies to every vhost on the machine.
  • Rate limits keyed on $server_name, not $host, so a catch-all server cannot be used to mint unlimited keys and fill the zone.
  • Fix "listen ... http2" directive is deprecated by using http2 on;.

Feature

  • sites-available/bubbly_default.conf, the catch-all default server, enabled once per machine.
  • Brotli, opt-in alongside gzip, in directive/bubbly_brotli.conf. Off by default because without libnginx-mod-http-brotli-filter installed, brotli on; stops Nginx loading any config at all.
  • directive/bubbly_real-ip.conf, for deployments behind a proxy or CDN, where otherwise every log line and every rate limit sees only the proxy.
  • One PHP upstream per supported branch in conf.d/php_sockets.conf, selectable per site, so several PHP versions can be served from one machine.
  • Per-site rate limit zones keyed on $server_name$binary_remote_addr alongside the machine-wide ones, so one site cannot spend another's budget.
  • A landing page, published to GitHub Pages.

Improvement

  • Rewrite the README around the six-step install, with a Requirements table, a Configuration table giving advice per change, and sections on proxies, rate limiting and PHP versions.
  • Offer the unreferenced includes commented out at the bottom of bubbly_https.conf, so most configuration is uncommenting a line and reloading.
  • Trim the rate limit zones to one of each kind, sized by how long a state lives rather than by traffic volume, and name them for what they key on.
  • Note throughout that limits never apply to a return response, and that HTTP/2 collapses a page load to one connection.
  • Move the expires map to conf.d/expires-map.conf and modernise its types.
  • Refresh mime.types.
  • Document every [OPTION], [DEFAULT] and [WARNING] in prose rather than in shorthand, and delete the comments that had gone stale.

Supporting

  • Add .github/workflows/nginx.yml: nginx -t and a live probe of the templates on ubuntu:26.04 and debian:13, covering SNI certificate selection, the default server's refusals, ACME passthrough, per-site PHP upstream selection, rate limit isolation, Brotli and gzip negotiation, and the three per-vhost TLS behaviours; plus drift checks on duplicated values and a check that the default PHP socket matches the platform's real pool.
  • Add CI for Markdown, shell, JSON, CSS, JS and HTML, and a security workflow.
  • Add Dependabot.
  • Replace Code Climate and Codacy with qlty.
  • Update the Qualys SSL Labs and Security Headers screenshots.
  • Adopt the organisation's shared Code of Conduct, Security Policy and issue templates from eustasy/.github.

Changes

2.2.0 to 3.0