Disclaimer: Analyzed and Summarized with AI
After a power outage at home my node came back up but two modules did not: Passbolt and Nextcloud, both returning Bad Gateway through Traefik. I diagnosed both and found the same mechanism, so I am posting it here as @mrmarkuz suggested — maybe others have seen it too.
Environment
- NS8 core 3.21.0
- Modules: passbolt1 1.0.6, nextcloud1 1.7.3 → 1.7.4
- Node: single node, Proxmox VM, 16 GiB RAM
- Images: passbolt 5.14.3-1-ce-non-root, mariadb 10.11.18 (Passbolt), mariadb 10.6.27 (Nextcloud)
- Storage: ZFS RAIDZ1. Relevant caveat: at the time of the incident the pool was two Samsung SSDs plus one WD Red HDD — mid-migration to all-SSD. In a RAIDZ1 the slowest member sets the pace, so write latency was at HDD level. See the measurement at the end.
I had rebooted this system many times before without trouble. What was new was an unclean shutdown.
The common mechanism
None of the affected units set their own TimeoutStartSec, so the user manager default applies: DefaultTimeoutStartUSec=1min 30s. That limit applies per phase — every ExecStartPre and every ExecStartPost gets its own 90 s.
After an unclean shutdown InnoDB needs crash recovery, and all modules start concurrently competing for the same I/O. The budget runs out before the work finishes.
Case 1 — Passbolt: the DB phase times out and the service cannot self-recover
passbolt-db.service uses an unbounded wait loop as its second ExecStartPost:
while ! mysqladmin ping -h localhost -P 3306 -u root; do sleep 1; done
It has no timeout of its own and is terminated only by the systemd start timeout. Journal:
08:43:30 ping loop begins
08:44:56 passbolt-db.service: start-post operation timed out. Terminating. (86 s)
08:45:16 InnoDB: Starting crash recovery from checkpoint LSN=33302486
08:45:41 InnoDB: To recover: 246 pages
08:45:58 mariadbd: ready for connections
08:46:06 State 'stop-sigterm' timed out. Killing. → SIGKILL
08:46:47 Failed with result 'timeout', restart counter 1
The database became available 62 s after systemd had given up, and was SIGKILLed 8 s after announcing readiness. That kill leaves the redo log unclean again, so the next automatic retry needs crash recovery once more and hits the same timeout. Restart=always kept the service in that loop (retries at 08:47:50, 08:55:42, 09:03:07, all failed). Only a manual module restart broke it — the start at 09:11:38 completed in 34 s because by then the redo log was clean.
Case 2 — Nextcloud: pod creation times out first, then nginx burns its retry counter
08:44:49 Starting nextcloud.service
08:46:19 start-pre operation timed out (exactly 90 s)
Consequence: nextcloud-app.service fails with result 'dependency' because the pod does not exist. nextcloud-nginx starts anyway with --volumes-from nextcloud-app and fails with:
Error: looking up container "nextcloud-app" for volumes-from: no container with name or ID "nextcloud-app" found
Ten times in a row, then Start request repeated too quickly — after which Restart=always no longer applies and the unit sits in failed state indefinitely. nextcloud-db.service hit the same start-post timeout as Passbolt.
Case 3 — the same timeout hits a normal module update
This one worries me most. Updating nextcloud1 from 1.7.3 to 1.7.4 through the Software Center: the Software Center reports 100 % success, but the instance is left in maintenance mode with needsDbUpgrade: true and is unreachable.
10:04:28.811 nextcloud-app: Turned on maintenance mode
10:05:53.852 nextcloud-app.service: start-post operation timed out. Terminating.
10:07:24.102 State 'stop-sigterm' timed out. Killing.
The entrypoint enables maintenance mode and starts the occ upgrade; 85 s later systemd aborts the start phase and 90 s after that sends SIGKILL — killing the upgrade mid-migration. Running occ upgrade manually afterwards completed it. A schema migration being killed by a start timeout is a data risk, not just an availability problem, and a silently failed update reported as successful is hard to notice.
This has now happened on two independent systems here.
Case 4 — nextcloud-app never gets through its post-start chain
After a second power outage the same day (this time with a ZFS resilver running, so the pool was genuinely slow), nextcloud-app.service stayed in activating (start-post) indefinitely while the container itself was healthy (fpm is running, ready to handle connections).
The unit has three post-start steps sharing one 90 s budget:
ExecStartPost=runagent wait-startup
ExecStartPost=runagent setup-smtp
ExecStartPost=runagent setup-ldap
setup-ldap sets every LDAP config value through its own occ call, each spawning a PHP process in the container — observed at roughly 1 s per call (occ ldap:set-config s01 ldapGroupDisplayName cn, then occ ldap:set-config s01 ldapPort 20001, …). With ~20 values plus the other two steps the budget is gone, systemd kills the service, and the chain restarts from scratch. It never converges. A drop-in with TimeoutStartSec=1800 let it finish.
While reading wait-startup (~/.config/bin/wait-startup) I noticed three things:
- The
exceptbranch callscontinuebeforetime.sleep(1)— on errors the loop burns all 60 attempts in milliseconds instead of 60 seconds. - After 60 unsuccessful attempts the script exits 0. Failure to reach the app is not signalled.
- There is no timeout on the individual
occcall. One hanging call blocks the wholeExecStartPostuntil systemd aborts at 90 s.
What it is not
- Not memory pressure. 16 GiB total, ~2.4 GiB used, swap untouched.
status=137matches the SIGKILL frompodman stop -t 10inExecStop, not OOM. - Not a corrupt database. Crash recovery completed cleanly every time.
- Not modified units. No drop-ins were present before I added one, and I have since removed it.
On the disk speed question — with a measurement
@mrmarkuz pointed out that fast disks are a system requirement and that the apps are tested on fast storage. That is fair and it applies here: with one HDD still in the RAIDZ1, pod create took 65 s, container remove 40 s, and a trivial printf into a container 21 s.
The disk migration is now complete — all-SSD RAIDZ1, resilver finished without errors. Reference measurement on the same node:
# time runagent -m nextcloud1 systemctl --user restart nextcloud-app.service
real 0m26.955s
27 s for the full restart including the three-step post-start chain, i.e. comfortably inside the 90 s default with a factor of ~3.3 to spare. So slow storage was clearly what pushed these operations past the limit.
My argument is that slow storage exposes these issues rather than causing them:
- SIGKILLing a mariadbd that reported
ready for connections8 s earlier is what turns one slow start into a non-converging loop. A failed start should not leave the data directory in a state that guarantees the next attempt is slow too. - A schema migration should not run under a start timeout at all.
- A module update killed mid-migration should not be reported as successful.
Note the 27 s figure was measured with a clean database, no crash recovery, and no other modules starting simultaneously. Those three conditions together are what broke the 90 s budget.
Suggestions
- Explicit
TimeoutStartSecon the DB units, sized for crash recovery on a loaded host rather than a warm start. - Bound the readiness wait itself (the
mysqladmin pingloop, theocc statusloop) so it is not governed by the systemd start timeout as a side effect. - Avoid SIGKILLing a running mariadbd on start failure.
- Nextcloud:
nginxretrying ten times against a non-existentnextcloud-appand burning its retry counter is fragile — a dependency would be more robust than a blind retry. - Consider whether
setup-ldapneeds oneoccinvocation per config value.
Already fixed
@mrmarkuz confirmed a separate typo I found in nextcloud-nginx.service (%t/nextcloud-.pid instead of %t/nextcloud-nginx.pid, so the cleanup line never matches the actual PID file) and opened a PR: Fix typo by mrmarkuz · Pull Request #257 · NethServer/ns8-nextcloud · GitHub
Also, for anyone hitting the maintenance-mode case: you can simply run occ upgrade — no need for the full podman exec -u www-data nextcloud-app php occ upgrade I used.
Happy to provide full journals for any of the four cases.