Index: deploy/MULTI-INSTANCE.md
===================================================================
--- deploy/MULTI-INSTANCE.md	(revision ccaa5301c212451f2e6b7c28c49282055278e961)
+++ deploy/MULTI-INSTANCE.md	(revision 5462bab0a30abfb9338812cb154a11cebda17b67)
@@ -133,4 +133,16 @@
 ```
 
+**Migrated before August 2026?** Then your `klonkt-update` still restarts the
+retired `klonkt.service`: the code updates but the running process never
+follows, and after the next update the site can 500 on pages whose template
+and route no longer match. Repair it once:
+
+```bash
+sudo systemctl restart klonkt@<slug>                        # load the current code now
+sudo bash /opt/klonkt/scripts/klonkt-refresh-updater.sh     # fix the updater for good
+```
+
+The migration script does this by itself nowadays.
+
 That pulls the code once into `/opt/klonkt`, reinstalls dependencies only when
 `package-lock.json` changed, and restarts every instance it finds under
Index: scripts/install.sh
===================================================================
--- scripts/install.sh	(revision ccaa5301c212451f2e6b7c28c49282055278e961)
+++ scripts/install.sh	(revision 5462bab0a30abfb9338812cb154a11cebda17b67)
@@ -305,36 +305,9 @@
 
 log "Update command 'klonkt-update'…"
-cat > /usr/local/bin/klonkt-update <<EOF
-#!/usr/bin/env bash
-set -euo pipefail
-D="${KLONKT_DIR}"
-B=\$(runuser -u ${KLONKT_USER} -- git -C "\$D" rev-parse HEAD 2>/dev/null || true)
-runuser -u ${KLONKT_USER} -- git -C "\$D" fetch --depth 1 origin ${KLONKT_BRANCH}
-runuser -u ${KLONKT_USER} -- git -C "\$D" checkout -qf -B ${KLONKT_BRANCH} FETCH_HEAD
-A=\$(runuser -u ${KLONKT_USER} -- git -C "\$D" rev-parse HEAD)
-if [ "\$B" = "\$A" ]; then
-  echo "Klonkt is already up to date (\$A) — nothing to do."
-  exit 0
-fi
-if ! runuser -u ${KLONKT_USER} -- git -C "\$D" diff --quiet "\$B" "\$A" -- package-lock.json 2>/dev/null; then
-  runuser -u ${KLONKT_USER} -- env HOME="\$D" bash -c "cd '\$D' && npm ci --omit=dev"
-fi
-# Restart every instance. Each directory under the data root with an .env is one
-# instance sharing this checkout. An install that has not been split yet has no
-# such directories and still runs the single klonkt.service.
-N=0
-for d in ${KLONKT_DATA_ROOT}/*/; do
-  [ -f "\$d/.env" ] || continue
-  s=\$(basename "\$d")
-  systemctl restart "klonkt@\$s" && N=\$((N+1))
-done
-if [ "\$N" = 0 ]; then
-  systemctl restart klonkt
-  echo "Klonkt updated (\$A) + restarted."
-else
-  echo "Klonkt updated (\$A) + restarted \$N instance(s)."
-fi
-EOF
-chmod +x /usr/local/bin/klonkt-update
+# Generated by the shared script so an install and a later layout migration
+# can never drift apart on what the updater restarts.
+KLONKT_DIR="$KLONKT_DIR" KLONKT_USER="$KLONKT_USER" \
+KLONKT_DATA_ROOT="$KLONKT_DATA_ROOT" KLONKT_BRANCH="$KLONKT_BRANCH" \
+  bash "$KLONKT_DIR/scripts/klonkt-refresh-updater.sh"
 ok "klonkt-update"
 
Index: scripts/klonkt-migrate-data.sh
===================================================================
--- scripts/klonkt-migrate-data.sh	(revision ccaa5301c212451f2e6b7c28c49282055278e961)
+++ scripts/klonkt-migrate-data.sh	(revision 5462bab0a30abfb9338812cb154a11cebda17b67)
@@ -135,4 +135,17 @@
 run "systemctl enable --now 'klonkt@$SLUG'"
 
+step "Rewriting klonkt-update for the new layout"
+# The installer generated an updater that restarts klonkt.service — which we
+# just retired. Left alone it would keep updating the code while never
+# restarting the real process: half old, half new, and a 500 with no obvious
+# cause. Rewrite it so it restarts every klonkt@<slug> instead.
+if [ -f "$KLONKT_DIR/scripts/klonkt-refresh-updater.sh" ]; then
+  run "KLONKT_DIR='$KLONKT_DIR' KLONKT_USER='$KLONKT_USER' KLONKT_DATA_ROOT='$DATA_ROOT' bash '$KLONKT_DIR/scripts/klonkt-refresh-updater.sh'"
+else
+  say "WARNING: scripts/klonkt-refresh-updater.sh missing in this checkout."
+  say "         Update the code and run it once by hand, or every klonkt-update"
+  say "         from now on will update code WITHOUT restarting the process."
+fi
+
 step "Verifying"
 if [ "$DRY" = 1 ]; then
Index: scripts/klonkt-refresh-updater.sh
===================================================================
--- scripts/klonkt-refresh-updater.sh	(revision 5462bab0a30abfb9338812cb154a11cebda17b67)
+++ scripts/klonkt-refresh-updater.sh	(revision 5462bab0a30abfb9338812cb154a11cebda17b67)
@@ -0,0 +1,59 @@
+#!/usr/bin/env bash
+#
+# (Re)write /usr/local/bin/klonkt-update so it matches how this server runs.
+#
+# Why this exists: the updater is generated once at install time. A server
+# that later migrated to the split layout (klonkt@<slug> units) kept its old
+# updater, which still restarts the retired klonkt.service. Result: the code
+# on disk updates, the restart quietly fails, and the old process keeps
+# serving — half old routes, half new templates, which is how you get a 500
+# on one page and nothing in the logs that says why.
+#
+# Idempotent; safe to run any time:
+#
+#     sudo bash /opt/klonkt/scripts/klonkt-refresh-updater.sh
+#
+set -euo pipefail
+
+KLONKT_DIR="${KLONKT_DIR:-/opt/klonkt}"
+KLONKT_USER="${KLONKT_USER:-klonkt}"
+DATA_ROOT="${KLONKT_DATA_ROOT:-/var/lib/klonkt}"
+# Follow whatever branch the checkout is on (stable for most self-hosters).
+BRANCH="${KLONKT_BRANCH:-$(git -C "$KLONKT_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || echo stable)}"
+
+[ "$(id -u)" = 0 ] || { echo "run this as root (sudo)." >&2; exit 1; }
+[ -d "$KLONKT_DIR/.git" ] || { echo "no git checkout at $KLONKT_DIR" >&2; exit 1; }
+
+cat > /usr/local/bin/klonkt-update <<EOF
+#!/usr/bin/env bash
+set -euo pipefail
+D="${KLONKT_DIR}"
+B=\$(runuser -u ${KLONKT_USER} -- git -C "\$D" rev-parse HEAD 2>/dev/null || true)
+runuser -u ${KLONKT_USER} -- git -C "\$D" fetch --depth 1 origin ${BRANCH}
+runuser -u ${KLONKT_USER} -- git -C "\$D" checkout -qf -B ${BRANCH} FETCH_HEAD
+A=\$(runuser -u ${KLONKT_USER} -- git -C "\$D" rev-parse HEAD)
+if [ "\$B" = "\$A" ]; then
+  echo "Klonkt is already up to date (\$A) — nothing to do."
+  exit 0
+fi
+if ! runuser -u ${KLONKT_USER} -- git -C "\$D" diff --quiet "\$B" "\$A" -- package-lock.json 2>/dev/null; then
+  runuser -u ${KLONKT_USER} -- env HOME="\$D" bash -c "cd '\$D' && npm ci --omit=dev"
+fi
+# Restart every instance sharing this checkout: one directory with an .env
+# under the data root per instance. No instances there = the pre-split
+# single-service layout, which still runs plain klonkt.service.
+N=0
+for d in ${DATA_ROOT}/*/; do
+  [ -f "\$d/.env" ] || continue
+  s=\$(basename "\$d")
+  systemctl restart "klonkt@\$s" && N=\$((N+1))
+done
+if [ "\$N" = 0 ]; then
+  systemctl restart klonkt
+  echo "Klonkt updated (\$A) + restarted."
+else
+  echo "Klonkt updated (\$A) + restarted \$N instance(s)."
+fi
+EOF
+chmod +x /usr/local/bin/klonkt-update
+echo "klonkt-update rewritten: branch ${BRANCH}, code ${KLONKT_DIR}, instances under ${DATA_ROOT}"
Index: src/routes/admin-sites.js
===================================================================
--- src/routes/admin-sites.js	(revision ccaa5301c212451f2e6b7c28c49282055278e961)
+++ src/routes/admin-sites.js	(revision 5462bab0a30abfb9338812cb154a11cebda17b67)
@@ -211,4 +211,5 @@
     platforms: listPlatforms(),
     parsedLinks: [],
+    apAliases: '',
     error: null,
   });
Index: src/views/pages/admin-site-edit.ejs
===================================================================
--- src/views/pages/admin-site-edit.ejs	(revision ccaa5301c212451f2e6b7c28c49282055278e961)
+++ src/views/pages/admin-site-edit.ejs	(revision 5462bab0a30abfb9338812cb154a11cebda17b67)
@@ -217,5 +217,8 @@
       <p class="form-hint"><%= t('asite.aliases_hint') %></p>
       <label>
-        <textarea name="ap_aliases" rows="3" placeholder="@oud@mastodon.social&#10;https://andere-klonkt.example/ap/users/naam"><%= apAliases %></textarea>
+<%# typeof-guard on purpose: during a deploy an old route can render this new
+    template for one request (view cache fills on first hit). A missing local
+    must degrade to an empty field, not take the whole page down with a 500. %>
+        <textarea name="ap_aliases" rows="3" placeholder="@oud@mastodon.social&#10;https://andere-klonkt.example/ap/users/naam"><%= typeof apAliases !== 'undefined' ? apAliases : '' %></textarea>
       </label>
     </fieldset>
