Det her er fortsættelsen på »Jeg troede jeg var blevet hacket«. I det forrige indlæg ryddede jeg op på kassen, reclaimede 13 TB lager — og plantede uden at vide det en tidsindstillet bombe i boot-konfigurationen. Her går den af.
- maj 2026. Min Hetzner-dedikerede kasse har kørt min agentiske AI-stack — Coolify, Docker-in-Docker-devcontainere, det hele — i syv dage i træk. Kl. 21:43 dør den i flammer: kernel page allocation failure af order:10 (en 4 MB sammenhængende chunk), tre OOM-killer-invokeringer mod løbske Next.js dev-servere (28 GB virtuel ×2) og Node-processer (55 GB virtuel), og en reboot.
Den del regnede jeg med at komme over på minutter. Hvad der faktisk skete derefter åd resten af min nat — og årsagen var en ændring, jeg havde lavet bevidst, med omhu, fem dage forinden.
Fem dage forinden: partitions-beslutningen
En uge før crashet havde jeg opdaget, at Hetzners installimage havde sat alle tre diske (én ~954 GB NVMe og to 7,3 TB HDD'er) op som ét RAID5 — hvilket begrænsede brugbart lager til ~1,9 TB og efterlod cirka 13 TB HDD-kapacitet uadresseret. Så jeg gjorde arbejdet for at få den kapacitet igen: konvertér HDD'erne fra MBR til GPT in-place, byg et nyt RAID1-array, montér som /mnt/bulk.
Jeg gjorde det omhyggeligt. Fuld block-level-backup til en Synology-NAS via Tailscale først. Jeg tjekkede boot-mode (Legacy BIOS, ikke UEFI). Og jeg ræsonnerede, korrekt så langt det rakte, at MBR→GPT-konverteringen ville ødelægge GRUB på sda og sdb (GPT-strukturen overlapper med MBR-boot-koden), men at NVMe'en (som jeg ikke rørte) stadig ville boote, og at BIOS ville falde igennem til den, efter sda/sdb fejlede.
Konverteringen gik glat. RAID forblev [UUU]. Kassen kørte videre i fem dage.
22. maj: den døde
OOM-kaskaden ovenfor. Det interessante er, hvad der kom bagefter.
Hardware-reset fra Hetzner Robot-panelet. Vent tre minutter. SSH:
$ ssh root@projects.si14agents.com
ssh: connect to host projects.si14agents.com port 22: Connection timed out
OK, nogle gange er RAID-assembly langsom efter en uren nedlukning. Vent fem minutter. Det samme. Ti. Det samme.
Aktivér Hetzners Rescue-system (et lille Debian, PXE-bootet af deres netværk). Det kommer fint op — bekræfter, at kassen er i live, bare ikke booter fra disk. Fra rescue mounter jeg diskene. RAID sundt. Filsystemer rene. Kerner og initramfs til stede i /boot. grub.cfg ser fin ud.
Hypotesen jeg gravitiserede mod: et par uger forinden havde apt installeret en ny kerne, som boxen aldrig var rebootet ind i, plus en Synology-modificeret initramfs. Nem historie: ny kerne + modificeret initramfs = boot-fejl. Jeg håndredigerede grub.cfg til den gamle kerne. Rebootede.
Intet.
Timer med mass-masking
Overbevist om at booten hang på en stuck service, gik jeg tilbage i rescue og maskede alt — docker, containerd, Synology, tailscale, fail2ban, coolify-runneren. Strippede booten ned til næsten-vanilla Ubuntu. Rebootede. Stadig intet.
Det, der endelig afslørede det egentlige problem, var journalctl --list-boots fra den mounted disk. Den persistente journal viste præcis tre boot-ID'er: den oprindelige gode fra 15. maj, den forrige gode der endte ved OOM-crashet 22. maj 21:43, og… intet andet. Ingen af mine reboot-forsøg havde nået systemd-journald.
Det er ikke en stuck service. Booten når ikke engang frem til systemd. Hvad end der fejler, sker tidligere — ved kernen, eller under.
KVM afslører det endelig
Jeg havde nægtet at eskalere til Hetzner for KVM, fordi kassen var »levende nok« til at PXE-rescue. Efter timers blindspor gav jeg op og bestilte en manuel power-cycle — som Hetzner heller ikke kunne få til at virke, så de koblede en gratis KVM-konsol på i tre timer.
Inden for minutter så jeg den faktiske skærm under boot. Efter BIOS-POST, fem-sekunders nedtælling, valg af »Boot from first hard disk«, og så:
Type 'exit' to get the menu back
iPXE>
Den faldt direkte tilbage til iPXE-shellen. Hvilket betød: BIOS var sat op med netværk som Boot Option #1. NIC'ens PXE-ROM gav stafetten til Hetzners iPXE. iPXE's »boot fra første harddisk« forsøgte at chainloade fra sda — fandt ingen MBR-boot-kode (fordi jeg havde slettet den fem dage forinden, da jeg konverterede sda til GPT) — og dumpede mig i iPXE-shellen.
iPXE falder ikke videre til næste disk af sig selv. Det var den del, jeg havde misset i mit »BIOS falder igennem til NVMe«-ræsonnement. Native BIOS ville have. Hetzners iPXE-chainloader prøver den første disk og giver op.
Systemet havde kun »virket« i fem dage, fordi intet havde trigget boot-stien i den tid. OOM-crashet var den første reboot siden konverteringen — og første gang iPXE fik chancen for at fejle.
Den faktiske fiks
Fra rescue: mount disken, chroot, geninstallér GRUB på den disk, der stadig er i god form (grub-install --target=i386-pc /dev/nvme0n1 virker; sda/sdb fejler, fordi de nu er GPT uden en BIOS Boot Partition — en fix-it-later-TODO). Men GRUB på NVMe'en er ikke nok i sig selv — BIOS skal faktisk prøve NVMe'en før netværket. Så: KVM ind i boot-menuen (F11), vælg NVMe manuelt, se Ubuntu boote. Så ind i BIOS Setup, skift rækkefølgen til NVMe først og deaktivér LAN PXE Boot.
Gem, exit, reboot. Booter rent. Cirka fire timer fra OOM-crash til »alt grønt igen«.
Læringen
Bomben jeg plantede sad i ro i fem dage, indtil en OOM tvang den første reboot — og valgte tidspunktet for mig. Det er den dybe lektie: en ændrings blast radius kan overleve det vindue, hvor du laver ændringen.
Vis verificeringen, mass-masking og BIOS-rækkefølgen
Fem dage forinden: jeg tjekkede at alle tre diske var bootbare
$ dd if=/dev/nvme0n1 bs=512 count=1 2>/dev/null | strings | head
GRUB
$ dd if=/dev/sda bs=512 count=1 2>/dev/null | strings | head
GRUB # ← også her
$ dd if=/dev/sdb bs=512 count=1 2>/dev/null | strings | head
GRUB # ← og her
Alle tre havde GRUB stage 1 i deres MBR — standard Hetzner-RAID-install, GRUB på hver member, så systemet overlever et enkelt disk-svigt. Jeg vidste, at konverteringen ville fjerne den fra sda/sdb. Jeg vidste bare ikke, at iPXE ikke ville falde videre til NVMe'en.
Mass-masking fra rescue
systemctl --root=/mnt/root mask docker.service docker.socket containerd.service \
synology-active-backup-business-linux-service.service \
tailscaled.service fail2ban.service unattended-upgrades.service \
'actions.runner.pksorensen-agentic-live-www.coolify-runner.service'
echo 'blacklist synosnap' > /mnt/root/etc/modprobe.d/blacklist-synosnap.conf
sed -i '/\/mnt\/bulk/s/^/#/' /mnt/root/etc/fstab
Alt sammen spildt arbejde — fejlen var under systemd, ikke i userspace. Bagefter unmaskede jeg det hele igen.
BIOS-boot-rækkefølgen, før og efter
Boot Option #1: SAMSUNG MZVL21T0HCLR-00B00 (var: IBA CL Slot 00FE = netværk)
Boot Option #2: P0: HGST HUH721008ALE600
Boot Option #3: P1: HGST HUH721008ALE600
LAN PXE Boot Option ROM: Disabled (var: Enabled)
Hvad jeg tog med
- En ændrings blast radius kan overleve ændringsvinduet. Jeg ræsonnerede korrekt, at GPT-konverteringen ville fjerne GRUB fra sda/sdb. Det jeg ikke tog højde for, var kæden af hvad der sker, når BIOS er netværks-først og Hetzners iPXE er den, der prøver at chainloade — det lå uden for det system, jeg ændrede. Test dine antagelser ved faktisk at reboote — helst lige efter en ændring, på et tidspunkt du selv vælger, ikke når en OOM vælger det for dig.
- Diagnosticér ikke nedstrøms, når problemet er opstrøms. Jeg brugte timer på initramfs, services, kernel-moduler — alt sammen ting der kører efter GRUB. Den faktiske fejl var, at GRUB aldrig blev loadet.
journalctl --list-bootsuden poster efter crashet ville have udelukket userspace en time tidligere. - En remote grafisk konsol er guld værd. Jeg undgik at eskalere til KVM, fordi kassen var levende nok til PXE-rescue. Det var den forkerte ramme: et system, der kan disk-rescue men ikke disk-boote, er præcis hvornår KVM er mest værdifuld. Du skal kunne se, hvad firmwaren laver.
- GPT-diske kræver en BIOS Boot Partition for at være BIOS-bootbare. En lille (~1 MB) partition med
bios_grub-flaget er nok. Jeg burde have lavet en på hver HDD som del af GPT-konverteringen. (Ægte TODO.) - Verificér at den kørende kerne matcher boot-defaulten.
*** System restart required ***-noten betyder, at din kørende kerne og din boot-default er divergeret. Det er en mine, der kun udløses ved reboot — det værst tænkelige tidspunkt at opdage en utestet kombination. Periodiske bevidste reboots fanger det, før en OOM gør det for dig.
Kassen har været stabil siden denne recovery — og holdt stabil i 22 dage, før den næste katastrofe, som er en helt anden historie.
