AstroBaaS

Run it

Maintenance windows

Generated from MAINTENANCE.md in the AstroBaaS repository. The repository is the source of truth; this page is a copy of it.

Two different outages need two different answers, and only one of them can be served by the application.

ProcessServed byCovers
In-apprunningAstroBaaSmigrations, plugin installs, planned windows
Staticstoppedyour reverse proxythe redeploy itself

An in-app maintenance page cannot cover a redeploy, because during a redeploy there is no app. Shipping only the first would be a comforting thing to have and useless in the minute it was bought for.


1. In-app

The emergency switch

MAINTENANCE_MODE=1
MAINTENANCE_MESSAGE="Upgrading the shop — back shortly."   # optional
MAINTENANCE_UNTIL=2026-08-25T18:00:00Z                     # optional

An environment variable, deliberately, because the commonest reason to need this page is that the database is migrating, busy, or unreachable — and a flag stored in the database would be unreadable exactly when it is needed. The check runs before anything touches storage or loads a plugin.

The scheduled window

Admin → Settings → Maintenance mode. A checkbox, a message, and an optional “expected back”. Use this for planned work; it needs a healthy database, which a planned window has by definition.

The difference that matters: during a scheduled window staff still see the real site, so you can check it before reopening. The emergency switch cannot offer that — it answers before any session is read, which is the whole point.

What visitors get

503 Service Unavailable, with Retry-After and Cache-Control: no-store.

Not 200. A success status tells a crawler that this is your content now; sites have lost rankings to a maintenance notice served with 200. 503 says “temporarily unavailable, come back”, which is both true and the thing search engines are built to handle. API callers get the same status as JSON with code: "MAINTENANCE", so a headless storefront does not have to parse HTML to find out.

What stays open, and why each one has to

PathBecause
/healthz, /readyz, /metricsa host that cannot health-check the process pulls it out of rotation, and your holding page becomes a connection error
/adminan operator locked out of the admin cannot turn maintenance off
/login, /api/auth/*logging in is how you reach the admin
/_astro/, /favicon*the admin behind the page is a real application

2. Static, for the redeploy itself

Generate the page from the same source as the in-app one, so a visitor sees the same screen mid-deploy as mid-migration:

npm run build:maintenance-page

It writes public/maintenance.html — self-contained, no CSS file, no fonts, no JavaScript, because it has to render when nothing else is available.

nginx

location / {
    proxy_pass http://127.0.0.1:4321;
    proxy_intercept_errors on;
    error_page 502 503 504 = @maintenance;
}

location @maintenance {
    root /srv/astrobaas/public;
    rewrite ^ /maintenance.html break;
    add_header Retry-After 120 always;
    add_header Cache-Control "no-store" always;
    return 503;
}

return 503 is doing real work here — without it nginx would serve the holding page with a 200 and undo the whole point.

Caddy

handle_errors {
    @down expression {err.status_code} in [502, 503, 504]
    handle @down {
        root * /srv/astrobaas/public
        rewrite * /maintenance.html
        header Retry-After 120
        header Cache-Control "no-store"
        file_server
    }
}

A deploy with no visible downtime at all

The page above is the fallback. If you would rather visitors saw nothing:

  1. MAINTENANCE_MODE=1 on the old process, so it stops serving writes.
  2. Deploy alongside, let the new process boot and pass /readyz.
  3. Switch the proxy over, stop the old one.

AstroBaaS runs migrations at boot, so the new process is the one that migrates — which is why step 1 matters: two processes writing through a schema change is the one thing this ordering avoids.