The Proxmox host in my lab runs 24/7. The machines around it don’t: the workstation and the bench boxes sleep when idle, and Wake-on-LAN is a layer-2 mechanism, so waking one remotely means something inside the LAN has to send the packet. My first wake path was the obvious one — a container on the hypervisor that publishes magic packets — and it worked right up until the day the machine I needed to wake was the hypervisor, down for a kernel upgrade. A recovery path that depends on the thing being recovered isn’t a recovery path. (Port-forwarding UDP from the internet is the other classic answer, and it violates the rule from the homelab networking post: VPN in, don’t port-forward.)
So the wake path is its own device — independent of every machine it might need to rescue, drawing about half a watt, costing less than lunch: an ESP32 that holds one MQTT subscription and broadcasts magic packets on command.
The shape of the thing
The ESP32 runs ESP-IDF — FreeRTOS underneath, lwIP for the network stack.
The application is honestly small: connect to Wi-Fi, keep a session to the
broker, and on a message to wol/wake/<host>, assemble the 102-byte magic
packet (6×0xFF + the target MAC sixteen times) and fire it at the
broadcast address on UDP port 9. The packet format and why the NIC’s
pattern-matcher can parse it while the CPU is off is
the previous post’s territory; this
post is about what it takes to make the sender dependable.
One wrinkle a segmented network adds: broadcasts stop at VLAN boundaries. Small embedded gadgets normally belong on the quarantined IoT segment, but a magic packet sent there dies at the VLAN edge — the wake gateway needs layer-2 adjacency with its targets. In a network carved up by trust, that placement is a deliberate, documented exception — the kind of thing you decide once, on purpose, instead of discovering at 2 AM.
MQTT choices that matter
The protocol details are in the MQTT internals post; here’s how they cash out in a real device:
QoS 1, not 0, not 2. A wake command that evaporates is a failed feature — QoS 0 is out. Duplicate delivery, on the other hand, is harmless: waking an awake machine is a no-op. That’s precisely the QoS 1 contract (at least once), and it’s cheaper than QoS 2’s four-way handshake. Idempotent command + QoS 1 is the sweet spot.
No retained messages on the command topic. This is the classic trap.
Retain a wake command and every reconnect replays it — the ESP32
reboots, resubscribes, and helpfully wakes the server you just shut down.
Commands are events, not state; retained messages are for state.
Last Will as a health signal. The broker publishes
wol/status/gateway = offline if the ESP32’s session dies. My monitoring
watches that topic — a dead wake-gateway discovered at 2 AM when you need
it is the worst possible discovery time.
The watchdog is the design
The failure mode of an always-on embedded device isn’t crashing — it’s wedging: Wi-Fi associated, TCP session zombied, task blocked on a semaphore that will never post. A crash reboots; a wedge lasts until you physically walk over, which defeats the entire point of a remote wake device.
So the design assumption is “this will wedge eventually,” and the answer is the ESP32’s hardware task watchdog. The main loop must check in every few seconds; the check-in is gated on actual liveness — broker connection up, subscription alive — not just “the loop is spinning.” If the session zombies, the check-in stops, the watchdog fires, and the device is back in a known-good state in about two seconds. Nobody walks anywhere.
That inversion — design for recovery, not for uptime — is the one idea from this build that generalizes to every unattended device.
Takeaways
- WoL’s layer-2 constraint means something must live on the LAN; make it the cheapest, lowest-power thing that can hold an MQTT session — and keep the wake path independent of the machines it recovers.
- VLAN segmentation and broadcast-based protocols fight: place the wake gateway with L2 adjacency to its targets, as a deliberate exception.
- Idempotent commands + QoS 1 give you reliability without QoS 2’s overhead. Never retain command messages — retain state, publish events.
- Use MQTT Last Will to monitor the monitor: a wake gateway that fails silently fails at the worst time.
- For unattended devices, gate the watchdog check-in on end-to-end liveness and let the reset be your recovery path.
- This device backstops the lab whose always-on host runs the AI coworker that maintains the site you’re reading. Every layer has a recovery story — next up, the same rule applied to logins.