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

Phone to MQTT broker to ESP32 to sleeping NIC phone / laptop anywhere publish wol/wake/… MQTT broker QoS 1 · no retain the LAN (L2 adjacency) ESP32 gateway ESP-IDF · ~0.5 W UDP :9 · 102 bytes sleeping host's NIC pattern match → power on

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.