Drop-in configuration files and setup scripts for Certbot. An A+ on SSL Labs and an A on SecurityHeaders.io — the same day you ship.
We'll template example.com through every command below.
Plain bash scripts. Plain Nginx config files. Read them top to bottom in five minutes. No DSL, no operator, no agent.
--deploy-hook, no cron.Certbot's systemd timer runs certbot renew twice a day. Bubbly's hook reloads Nginx on success. Set it. Forget it.
Stapling, forward secrecy, strict transport, and a key exchange list led by X25519MLKEM768. Secure defaults that don't exclude any clients.
HSTS, X-Frame-Options, Referrer-Policy, Permissions-Policy, and the cross-origin opener and resource policies pinned to same-origin. An immediate A on SecurityHeaders.io.
Certbot and acme.sh are great. They just don't ship the Nginx config you actually want. Bubbly does — plus the headers, and a default server that pins your security floor.
| Bubbly | Certbot, raw | acme.sh | Caddy | |
|---|---|---|---|---|
| Issues Let's Encrypt certs | ||||
| Auto-renews on a timer | systemd | cron | ||
| Nginx config that scores A+ | bring your own | bring your own | own server | |
| Session tickets | optional | |||
| Security headers pack | by default | manual | ||
| Stays vanilla — no daemon | replaces Nginx | |||
| Read the source in 5 min | bash + conf | Python | Go binary |
Bubbly composes with Certbot — it doesn't replace it. The scripts wrap
certbot certonly --webroot and rsync battle-tested config blocks into /etc/nginx/.
You'll need basic Nginx familiarity. You won't need to know Certbot, Let's Encrypt, the ACME spec, or how SSL works underneath.
http2 on and
OpenSSL 3.5+ for X25519MLKEM768. Check with
nginx -V, which reports the OpenSSL Nginx was built against, not the
one on your $PATH. Ubuntu 26.04 LTS and
Debian 13 clear both; Ubuntu 24.04 and Debian 12 clear
neither, and want Bubbly 2.2.0
instead. The config is written for Debian's layout —
Nginx here already includes sites-enabled/, so the symlinks below work as written.
Drop into $HOME, grab the dependencies, pull the repo.
Mirrors the repo's nginx-config/ into /etc/nginx/ — conf.d/, directive/, location/, groups/, and sites-available/ all land in place. Run it again whenever you pull a newer Bubbly.
conf.d/bubbly_ssl.conf by itself: the shared TLS session cache and the OCSP resolver. Protocols, groups and ciphers live in directive/ instead — see the next step for why.Once per server, before any site. It answers whatever no site claims: an unknown Host, a connection with no SNI, a probe at your bare IP. The rm drops the distribution's own default, which also claims default_server — with two, Nginx refuses to start.
sites-enabled/ file sorts first — and a new site sorting earlier would move them underneath you.Place the ACME challenge site so Let's Encrypt can find you.
example.com with your domain. Migrating a site that is already serving? Leave it running and add include location/bubbly_well-known-passthrough.conf; to it instead.Symlink it live, lint, reload.
It will ask for the root password and an email. Then it's done in a few seconds.
certbot renew twice daily. The deploy hook reloads Nginx after every successful renewal. No cron, no babysitting — which is why running the script by hand always passes --force-renew. Don't loop it: Let's Encrypt allows five certificates per identical set of names per week, and that limit cannot be raised. Add --dry-run to rehearse against staging.Add the HTTPS site alongside the HTTP one you already have. Keep example.com_http.conf symlinked — it answers ACME renewal challenges forever. Read the [OPTION]s and [WARNING]s; point the cert paths at your domain.
Skip this on a single server. Nginx 1.23.2+ generates ticket keys itself and rotates them in the shared session cache, which beats one static key. You only need your own when several Nginx instances behind a load balancer have to resume each other's tickets.
ticket.old.key and a fresh one is written, so tickets already issued keep working. Nginx encrypts with the first key listed and decrypts with any of them — keep the newest on top. Both are 600, since the key decrypts captured sessions.
That's it. The next time Let's Encrypt rotates your cert, Nginx will
reload itself. The next time you run nginx -t, it will pass.
Everything after this is optional — Brotli, per-site logs, a CSP, extra PHP
versions, limits behind a CDN — and each one is a line to uncomment, listed in
the README's Configuration table.
Go test it ↓
Tested against SSL Labs and SecurityHeaders.io with the defaults shipped in the repo — no extra tuning.
Strict-Transport-SecurityContent-Security-Policy opt-inX-Frame-OptionsX-Content-Type-OptionsReferrer-PolicyPermissions-Policy