What happened

Our nightly auto-patching (a systemd timer running dnf upgrade) deliberately does not reboot. On days when the batch includes packages like the kernel or glibc — things that only take effect after a restart — it just logs REBOOT-NEEDED and leaves the reboot to a human.

The 33 piled-up updates from my previous post included exactly that: kernel and glibc. Rebooting the production site follows a simple runbook: put up the maintenance page → reboot → verify → take the page down. The server itself is down for under a minute. Yet the maintenance page was actually visible for about 10 minutes.

The slow part wasn't the server. It was me.

Procedure v1 failed — maintenance ON came too early

My original procedure looked like this:

# v1: maintenance ON → update → reboot sudo touch /var/www/html/.maintenance # maintenance page ON (flag file) sudo dnf upgrade -y --releasever=latest # ← takes 1–2 minutes here sudo reboot 

Downloading and applying packages takes a minute or two, during which the server can serve traffic just fine — but visitors see a maintenance page. Pointless downtime from the user's perspective. So I swapped the order:

# v2: update → maintenance ON → reboot sudo dnf upgrade -y --releasever=latest sudo touch /var/www/html/.maintenance sudo reboot 

Now the only outage window is around the reboot itself — under a minute. In theory.

Measured: 10 minutes — the bottleneck was human

Step Expected Measured
dnf download/apply (before maintenance) 1–2 min ~2 min (no impact)
Maintenance ON → reboot → OS back up ~1 min ~50 s
Human reopens browser SSH and logs back in (not budgeted) ~9 min
Removal command (rm) seconds seconds

Checking uptime -s afterwards showed the reboot itself finished in about 50 seconds. The remaining 9 minutes were me: the browser SSH session drops on reboot, I reopen the tab, retry a few connection errors wondering "is it up yet?", get distracted by something else, and finally log in and type the removal command.

I had designed the procedure to minimize server downtime — but I was measuring the wrong thing. What determines how long the maintenance page stays up isn't the reboot time; it's the time until a human types the next command.

The fix — take the human out of the removal step

Two changes.

(1) Prepare the one-liner to paste after reconnecting

Right before rebooting, put the next command in a note:

sudo rm -f /var/www/html/.maintenance && curl -sk -o /dev/null -w '%{http_code}\n' https://localhost/ 

Reopen the session, paste, done. No "wait, what do I type after logging in?" time.

(2) A systemd oneshot that clears the maintenance flag after reboot

# /etc/systemd/system/maintenance-clear.service [Unit] Description=Clear maintenance flag after reboot After=httpd.service Requires=httpd.service [Service] Type=oneshot # Wait until localhost returns 200 before clearing the flag ExecStartPre=/usr/bin/bash -c 'for i in $(seq 1 30); do curl -sfk -o /dev/null https://localhost/ && exit 0; sleep 2; done; exit 1' ExecStart=/usr/bin/rm -f /var/www/html/.maintenance [Install] WantedBy=multi-user.target 

The key is the ExecStartPre wait. If you remove the flag unconditionally at boot, the maintenance page disappears before the app is up and visitors see a 502. Verify "healthy means exactly this value" first, then remove. If the health check never passes, the flag stays and the maintenance page remains — the failure mode is safe.

With this in place the human part of the runbook ends at "dnf → maintenance ON → reboot". I only come back later to check uname -r.

Verification

What I checked after the reboot:

uptime -s # reboot time (backs up the actual outage duration) uname -r # running on the new kernel? sudo dnf history info last # what was applied 

On top of that: during maintenance, I confirmed the four main pages returned 200 from inside using a bypass cookie; after removal, I recorded 200s and response times for the same four pages via their external URLs. The recovery time I log is not "when I removed the flag" but when the site looked healthy from outside.

Takeaways

  • Nightly auto-patching doesn't reboot by design. Kernel/glibc days need a human reboot
  • Turn maintenance ON right before the reboot — showing it during the download is pointless exposure
  • Measure downtime not as "time the server was down" but as "time until the human types the next command." Here: 50 seconds vs. 10 minutes
  • Bundle the removal into the reconnect one-liner, or automate it with a health-checked oneshot, and the "human" row disappears from the timetable

Add a "human" row to the time estimates in your runbooks. It's usually the longest one.