diff --git a/README.md b/README.md
index 44d662a..87282ea 100644
--- a/README.md
+++ b/README.md
@@ -35,6 +35,8 @@ The installer creates:
Every node checks `origin/main` about once per minute. When a new commit is available, Synapsis fast-forwards the checkout, replaces the single `backups/latest` database snapshot, installs dependencies, runs migrations, builds, and restarts automatically. The repository commit count is shown in the Network Info card; `/api/version` exposes both that number and the full deployed commit hash.
+While an update is being installed, `synapsis-maintenance.service` temporarily serves a branded maintenance page on the node's configured `PORT`. Existing reverse proxies continue receiving an HTTP response instead of showing a gateway error, and browsers automatically reload when Synapsis is ready.
+
For a node installed before automatic updates existed, bootstrap the timer once with:
```bash
diff --git a/deploy/install.sh b/deploy/install.sh
index c38484a..6994df1 100755
--- a/deploy/install.sh
+++ b/deploy/install.sh
@@ -59,6 +59,7 @@ runuser -u synapsis -- npm --prefix "$APP_DIR" run db:migrate
runuser -u synapsis -- npm --prefix "$APP_DIR" run build
install -m 0644 "$APP_DIR/deploy/synapsis.service" /etc/systemd/system/synapsis.service
+install -m 0644 "$APP_DIR/deploy/synapsis-maintenance.service" /etc/systemd/system/synapsis-maintenance.service
install -m 0644 "$APP_DIR/deploy/synapsis-update.service" /etc/systemd/system/synapsis-update.service
install -m 0644 "$APP_DIR/deploy/synapsis-update.timer" /etc/systemd/system/synapsis-update.timer
runuser -u synapsis -- git -C "$APP_DIR" rev-parse HEAD | install -m 0644 -o synapsis -g synapsis /dev/stdin "$DATA_DIR/deployed-commit"
diff --git a/deploy/maintenance-server.mjs b/deploy/maintenance-server.mjs
new file mode 100644
index 0000000..264a535
--- /dev/null
+++ b/deploy/maintenance-server.mjs
@@ -0,0 +1,64 @@
+import http from 'node:http';
+
+const host = process.env.MAINTENANCE_HOST || '127.0.0.1';
+const port = Number.parseInt(process.env.PORT || '43821', 10);
+
+const page = `
+
+
+
+
+
+ Update in progress
+
+
+
+
+ S
+ Update in progress
+ We’re making Synapsis better. We’ll be right back!
+ Checking for the node…
+
+
+
+`;
+
+const server = http.createServer((request, response) => {
+ response.writeHead(503, {
+ 'Content-Type': 'text/html; charset=utf-8',
+ 'Content-Length': Buffer.byteLength(page),
+ 'Cache-Control': 'no-store, max-age=0',
+ 'Retry-After': '30',
+ 'X-Robots-Tag': 'noindex',
+ });
+ if (request.method === 'HEAD') response.end();
+ else response.end(page);
+});
+
+server.listen(port, host, () => {
+ console.log(`Synapsis maintenance page listening on http://${host}:${port}`);
+});
+
+const shutdown = () => server.close(() => process.exit(0));
+process.on('SIGINT', shutdown);
+process.on('SIGTERM', shutdown);
diff --git a/deploy/synapsis-maintenance.service b/deploy/synapsis-maintenance.service
new file mode 100644
index 0000000..e05c92a
--- /dev/null
+++ b/deploy/synapsis-maintenance.service
@@ -0,0 +1,20 @@
+[Unit]
+Description=Synapsis update maintenance page
+After=network-online.target
+Wants=network-online.target
+Conflicts=synapsis.service
+
+[Service]
+Type=simple
+User=synapsis
+Group=synapsis
+WorkingDirectory=/opt/synapsis
+EnvironmentFile=/etc/synapsis.env
+ExecStart=/usr/bin/env node /opt/synapsis/deploy/maintenance-server.mjs
+Restart=on-failure
+RestartSec=1
+NoNewPrivileges=true
+PrivateTmp=true
+ProtectSystem=strict
+ProtectHome=true
+
diff --git a/deploy/uninstall.sh b/deploy/uninstall.sh
index 97f3db5..7eacc46 100755
--- a/deploy/uninstall.sh
+++ b/deploy/uninstall.sh
@@ -12,8 +12,10 @@ fi
systemctl disable --now synapsis-update.timer 2>/dev/null || true
systemctl stop synapsis-update.service 2>/dev/null || true
+systemctl stop synapsis-maintenance.service 2>/dev/null || true
systemctl disable --now synapsis 2>/dev/null || true
rm -f /etc/systemd/system/synapsis.service \
+ /etc/systemd/system/synapsis-maintenance.service \
/etc/systemd/system/synapsis-update.service \
/etc/systemd/system/synapsis-update.timer
systemctl daemon-reload
diff --git a/deploy/update.sh b/deploy/update.sh
index bcd0201..f2ed94b 100755
--- a/deploy/update.sh
+++ b/deploy/update.sh
@@ -21,7 +21,7 @@ set +a
install_update_units() {
units_changed=0
- for unit in synapsis.service synapsis-update.service synapsis-update.timer; do
+ for unit in synapsis.service synapsis-maintenance.service synapsis-update.service synapsis-update.timer; do
if ! cmp -s "$APP_DIR/deploy/$unit" "/etc/systemd/system/$unit"; then
install -m 0644 "$APP_DIR/deploy/$unit" "/etc/systemd/system/$unit"
units_changed=1
@@ -66,8 +66,15 @@ if [[ "${SYNAPSIS_APPLY_UPDATE:-0}" != "1" ]]; then
exec env SYNAPSIS_APPLY_UPDATE=1 APP_DIR="$APP_DIR" DATA_DIR="$DATA_DIR" ENV_FILE="$ENV_FILE" BRANCH="$BRANCH" bash "$APP_DIR/deploy/update.sh"
fi
+install_update_units
systemctl stop synapsis
-trap 'systemctl start synapsis' EXIT
+systemctl start synapsis-maintenance
+
+restore_service() {
+ systemctl stop synapsis-maintenance 2>/dev/null || true
+ systemctl start synapsis
+}
+trap restore_service EXIT
if [[ -f "${DATABASE_PATH:-$DATA_DIR/synapsis.db}" ]]; then
database_file="${DATABASE_PATH:-$DATA_DIR/synapsis.db}"
@@ -96,6 +103,7 @@ runuser -u synapsis -- npm --prefix "$APP_DIR" run build
deployed_commit="$(runuser -u synapsis -- git -C "$APP_DIR" rev-parse HEAD)"
printf '%s\n' "$deployed_commit" | install -m 0644 -o synapsis -g synapsis /dev/stdin "$DEPLOYED_COMMIT_FILE"
install_update_units
+systemctl stop synapsis-maintenance
systemctl start synapsis
trap - EXIT
echo "Synapsis updated successfully to ${deployed_commit:0:7}."