Skip to content

nginx site role writes http2 on; (nginx >= 1.25.1) and leaves an invalid vhost enabled when validation fails #66

Closed

Description

@romamo

What happened

Converging a new domain (domains.yml, cloudfall_nginx_site) on a Debian 12 host with nginx 1.22.1 wrote a virtual host containing http2 on;. That directive exists only from nginx 1.25.1, so nginx -t failed:

[emerg] unknown directive "http2" in /etc/nginx/sites-enabled/demo.cloudfall.dev.conf:19

The role's validation worked: nginx was not reloaded, and the running sites kept serving. But the failed vhost stayed in sites-available and enabled in sites-enabled, so the host's configuration on disk no longer passed nginx -t. Any later reload (a certbot renewal hook, another deploy, a restart after a reboot) would have failed for every site on that host. It was repaired by hand within minutes (listen 443 ssl http2;, the pre-1.25 form).

Two defects

  1. The template assumes nginx ≥ 1.25.1. Debian 12 ships 1.22.1. The vhost should use listen ... ssl http2; below 1.25.1 and http2 on; from it, keyed on the installed version (the role can read nginx -v or the package version), or leave HTTP/2 out when it cannot tell
  2. A vhost that fails validation is left enabled. When nginx -t fails after the vhost is written, the role should restore the previous file (or remove a new one and its sites-enabled link) before failing, so a failed converge never leaves the host's configuration invalid on disk

Reproduce

Debian 12, nginx from the distribution, any declared Domain with tls.mode: required, run domains.yml with certificate issuance.

Notes

Found deploying the hackathon demo to a production host; that host's vhost now differs from the template until this is fixed, so the next converge there must wait for the fix.

Activity

  1. shipmill-romamo commented on Oct 9, 2026

    @shipmill-romamo
    Contributor

    Triage: implement. Both defects are confirmed on main (070c781):

    1. templates/domain-site.conf.j2 always writes listen 443 ssl; plus http2 on; in the TLS server block, whatever nginx is installed
    2. tasks/main.yml renders the vhost, links it into sites-enabled, and only then runs nginx -t; a failed check stops the play with the new file and link left in place. This holds for both renders (the HTTP-only one before issuance and the TLS one after)

    Plan, one PR:

    • Read the installed nginx version on the host (nginx -v, which prints to stderr) after the install task, and fail the converge with a clear message when it can't be parsed, rather than guessing. The template writes listen 443 ssl http2; / listen [::]:443 ssl http2; below 1.25.1 and http2 on; from 1.25.1. A template test covers both sides of 1.25.1
    • Wrap render, enable and nginx -t in a block/rescue: before rendering, note whether the vhost file and its sites-enabled link existed and keep a backup of the previous file; on a failed nginx -t, restore the previous file (or remove a new file and the link it created), then fail with nginx's error. Nginx is never reloaded on a failed check, as now
    • No change to the Domain schema, the receipt, or the rendered output on nginx ≥ 1.25.1

    On the production host repaired by hand: the next converge rewrites the vhost from the template, which on its nginx 1.22.1 will then render the same listen 443 ssl http2; form, so it can run once this lands.

  2. shipmill-romamo commented on Oct 9, 2026

    @shipmill-romamo
    Contributor

    Triage: #67 fixes both defects. The role reads nginx -v and fails the converge when it can't parse it; below 1.25.1 the vhost uses listen 443 ssl http2;, and from 1.25.1 the output is byte-identical to before. Each render runs as a block/rescue behind nginx -t: a rejected render restores the previous file (or removes a new file and the link this run created), then fails without reloading.

    Trade-offs: nginx is now started only after the first nginx -t passes, and the certbot timer is enabled only after the final check passes. The rollback path is covered by task-level tests, lint and the syntax check, but has not yet run against a live host.

  3. shipmill-romamo commented on Oct 9, 2026

    @shipmill-romamo
    Contributor

    Released in v0.6.0rc12.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions