Files
werkator/docs/prs/2026-09-03-PR#17-maintenance-page.md
T
2026-09-03 17:49:21 +02:00

5.7 KiB

WARNING: This document describes only the change applied in this PR. It may already be outdated once the next PR is merged. Historic PR-documentation is not maintained along with new PRs — treat it as a snapshot, not as current documentation.

The Problem

instance-update restarts the systemd unit: for a few seconds nothing listens on Werkator's port at all. On the Hostsharing Managed Webspace deployment (see deployment.md) the platform's Apache sits in front and proxies via .htaccess (SystemdServiceFiles.htaccessContent); with the backend refusing connections, Apache answers with its own bare 502 error page, or the request just hangs until it times out. Either way the instance looks dead rather than "back in a moment", which is confusing during a deploy that is otherwise routine and fast (the restart itself is under a second; the rest of instance-update's time is upload).

Non-Goals

  • A live progress indicator ("2 of 10 seconds left") — the maintenance page is a static file with no way to know how far the restart has gotten.
  • Zero-downtime / blue-green deployment — the restart gap itself is not eliminated, only made to look intentional instead of broken.
  • The Docker-host and bwrap-webspace-without-a-domain deployment variants, which do not go through Apache/.htaccess at all.

The Scenarios

Feature: a refused connection shows a maintenance page instead of a bare error

Background

  • init --systemd writes the .htaccess only when server.publicBaseUrl is set — unchanged; the maintenance page is generated under the same condition, next to it.
  • The generated .htaccess already proxies every request to http://127.0.0.1:<port> via mod_rewrite [P]; a refused connection surfaces as Apache's own 502 (and, depending on Apache's timeout handling, 503/504).

Scenario#17.01: A refused backend connection serves the maintenance page

So that a deployment's restart window reads "please retry" instead of a bare error or a hang.

  • Given the generated .htaccess and werkator-maintenance.html are in a domain's docroot
  • When the proxied backend refuses the connection (Apache's 502/503/504)
  • Then Apache serves werkator-maintenance.html instead of its default error page
Verified by

Scenario#17.02: The maintenance page itself is never proxied

So that ErrorDocument's internal sub-request for the page does not loop back into the same rewrite rule (which would proxy it to the — still down — backend and fail again).

  • Given the generated .htaccess
  • When Apache serves werkator-maintenance.html as an ErrorDocument
  • Then the RewriteCond excludes exactly that path from the proxy RewriteRule
Verified by

Scenario#17.03: A host without a configured public base URL is unaffected

So that a developer machine or a Docker-host deployment, which never went through .htaccess, sees no new file and no behavior change.

  • Given server.publicBaseUrl is blank
  • When init --systemd runs
  • Then neither .htaccess nor werkator-maintenance.html is written — unchanged from before this PR
Verified by
  • Existing InitCommand behavior (server.publicBaseUrl.isNotBlank() gate); not separately tested, as before this PR.

The Solution

SystemdServiceFiles.htaccessContent gains three ErrorDocument lines (502/503/504) pointing at a new static werkator-maintenance.html, plus one RewriteCond excluding that file from the catch-all proxy rule. SystemdServiceFiles.maintenancePageContent() is a small, self-contained HTML page ("Werkator is restarting — please retry in a few minutes") — no external CSS/fonts/images, since nothing would be there to serve them while Werkator itself is the thing that is down. InitCommand.createSystemdFiles writes it under the same server.publicBaseUrl.isNotBlank() gate as the .htaccess, next to it in .git/werkator/. tools/remote instance-start copies both files into the domain docroot in the same step (it already copied the .htaccess there; the maintenance page rides along).

No config key was added: like the .htaccess itself, the maintenance page is generated whenever a publicBaseUrl is configured, not behind a separate toggle nobody is expected to turn off.

Open Questions

  • Does Apache actually reach 502, or does the client just time out first? Depends on Apache's ProxyTimeout/connect-timeout configuration on the managed webspace, which this PR does not change or configure — where Apache's own request timeout fires before the backend's refused-connection error would, the client still sees a hang, not the maintenance page, for that fraction of the restart window. Not addressed here; a follow-up could set an explicit short ProxyTimeout if this turns out to matter in practice.

Additional Changes

  • None beyond the feature itself.

Prerequisite PRs

  • None; builds on the existing .htaccess generation (SystemdServiceFiles, InitCommand), unchanged in shape.

Follow-up PRs

  • An explicit Apache ProxyTimeout if the client-side hang (see Open Questions) turns out to matter in practice.