Wake-on-LAN in a Modern Homelab
A protocol from 1997 that still solves your electricity bill

Contents
Wake-on-LAN is a protocol from 1997 that consists of sending a machine’s MAC address forty-eight times in a row inside a UDP packet nobody authenticates. There is no encryption, no acknowledgement, no error handling and no reply. You fire it into the ether and hope. By every standard of modern protocol design it should have been retired two decades ago.
I use it every single day, and it is the reason my electricity bill has a number in front of it that I am willing to say out loud.
The case is simple. I have a machine that draws around 120W and is genuinely needed for perhaps three hours a day — the box that does video transcoding, occasional model inference, and the weekly backup run. Left running, that is roughly 1,050 kWh a year. Woken on demand and put back to sleep, it is closer to 130. At UK electricity prices that difference is enough to fund the entire rest of the homelab, and the cost is a two-second delay before the thing I asked for starts happening.
That is the pitch. The rest of this is about the fact that WoL is quietly broken by half the features on your network, and how to find out which half.
The magic packet, demystified
The packet is six bytes of 0xFF followed by the target’s MAC address repeated sixteen times. That is the entire specification. It is sent as a broadcast on UDP port 9 (or 7, or as a raw ethernet frame — nothing agrees, and it mostly does not matter).
The network card, when powered down, keeps a trickle of standby power and a very simple pattern matcher running. It watches every frame that arrives for that repeated-MAC signature and, on a match, asserts a pin that tells the motherboard to power up. There is no CPU involved, no OS, no driver — the NIC is doing pattern matching on its own while the rest of the machine is off.
Note what is absent: any notion of who sent it. Anyone on your broadcast domain who knows a MAC address can power up your machines, all of them, repeatedly. The MAC is public information, sitting in every ARP table on the network. This is only tolerable because being on your L2 segment is already a serious position, and because the worst outcome is a machine turning on. It is still worth understanding that WoL is a feature you are granting to your entire local network, which is another reason IoT devices belong on a segment of their own.
Making the target wake up
Three layers must all agree, and they will not.
BIOS. Find the setting. It is called “Wake on LAN”, “Power On by PCI-E Device”, “Resume by PME”, “ErP” or, memorably on one board I own, “EuP 2013”. If ErP or EuP is enabled, it means the board complies with a standby power directive by cutting power to the NIC entirely, which disables WoL. So you want that one off. This inversion catches everybody.
The NIC. Check and enable:
| |
g is “wake on magic packet”. d is disabled, and d is the default on a depressing number of cards. Supports Wake-on: pumbg tells you the card can do it; if the g is missing from that list, the hardware cannot and no amount of configuration will change that.
Persistence. This is the step everyone misses, and it is why WoL “works until I reboot”. The ethtool setting is not persistent — the driver resets it on load. Make it stick:
| |
Then sudo update-initramfs -u and reboot. The .link file is applied by udev when the device appears, which is early enough to survive everything. Match on MAC rather than interface name, because predictable interface names are less predictable than the name suggests.
Verify after a reboot with ethtool enp3s0 | grep Wake-on. If it says d again, something is resetting it — usually NetworkManager, which has its own wake-on-lan property that will happily override yours.
Sending it
| |
If it does not work, the fastest diagnosis is to watch for the packet at the other end. On a machine on the same segment:
| |
If you see the packet arrive at that segment and the machine still does not wake, the problem is the machine. If you do not see it, the problem is the network, and the network is where the real fun is.
Everything that breaks it
This is the part worth the article. WoL was designed for a flat, dumb, broadcast network, and yours is none of those things.
Routers do not forward broadcasts. This is correct behaviour and it is the number one cause of “it works from my desktop but not from Home Assistant”. If the sender is on a different subnet from the target, the broadcast dies at the router. Options: a directed broadcast (many routers refuse, correctly), a helper on the target’s subnet, or — the sane answer — accept it and put a small always-on device on each segment that can send on your behalf.
Managed switches age out MAC entries. A machine that has been off for ten minutes is no longer in the switch’s forwarding table, so a unicast magic packet has nowhere to go. Broadcast works because broadcast floods. This is why wakeonlan defaults to broadcast, and why the clever unicast approach you found on a forum works for five minutes after shutdown and never again.
Green Ethernet / EEE. Energy-efficient ethernet features on switches and NICs can drop the link entirely when the port goes quiet. No link, no packet, no wake. If a machine wakes when you unplug and replug the cable but not otherwise, this is it. Disable EEE on that switch port.
Wi-Fi. Wake-on-Wireless-LAN exists, is a different feature with different hardware support, and works approximately never. Use a cable.
Fast Startup on dual-boot machines. If the machine last shut down from Windows with Fast Startup on, it is hibernated rather than off, and the NIC is in a state where WoL may not fire. Turn Fast Startup off.
The switch itself. Some cheap unmanaged switches drop the link on the uplink port when the downstream device powers off. Rare, infuriating, and only findable with a second switch.
Waking across subnets, properly
Since the router-drops-broadcasts problem is the one that stops most people, it is worth solving it properly rather than fighting it.
The wrong answer is directed broadcast — telling the router to forward a packet addressed to 192.168.1.255 from another subnet. Most routers refuse by default, and they refuse for a good reason: directed broadcast forwarding is the mechanism behind an amplification attack that made the late nineties unpleasant. Enabling it to save yourself a Raspberry Pi is a poor trade.
The right answer is a relay: a small always-on machine on the target’s own segment that accepts an authenticated instruction from you and emits the broadcast locally. This is one systemd socket unit away:
| |
| |
Bind it to localhost and reach it over an SSH tunnel or your mesh overlay, so the authentication is the overlay’s rather than something you invented. The relay script reads a MAC from stdin, validates it against a hard-coded allowlist, and calls wakeonlan. Twelve lines. The allowlist is the important part: an unvalidated relay is a way for anything that reaches port 9999 to power up arbitrary hardware, which is precisely the property you were trying to keep confined to one segment.
If you already run a mesh overlay with a subnet router on that segment, you have most of this for free — the subnet router is already the always-on machine in the right broadcast domain, and it only needs the twelve-line script.
Putting it to work
The sending half wants to be automatic. A wrapper that wakes on demand and gets out of the way:
| |
Sending three packets is deliberate: they are unacknowledged UDP broadcasts, they cost nothing, and one of them getting lost is a real thing that happens. Waiting for port 22 rather than for a ping is also deliberate — ICMP answers well before the machine is actually able to do anything for you.
The sleep half is the piece that makes it a system. A machine that never goes back to sleep is a machine you have added a wake script to for no reason:
| |
Run it from a timer every ten minutes. Getting the “am I busy?” checks right is the entire difficulty, and the failure mode is a machine that suspends mid-backup. Start conservative — more bail-out conditions than you think you need — and remove them only when you have watched it behave for a fortnight.
For the wake trigger, Home Assistant has a wake_on_lan integration that does this natively, which is where mine lives; I wrote up that side of it in powering servers on and off with Home Assistant.
Troubleshooting
Works from a cold shutdown, fails from suspend. Different power state, different code path. Some boards disable WoL in S3 but keep it in S5, or the reverse. Test both explicitly; do not assume one implies the other.
Worked, then stopped after a kernel update. The driver reset the WoL flag and your .link file is not being applied. Confirm with ethtool and check for NetworkManager overriding it.
Wakes on its own at random hours. Something is broadcasting the pattern, or the card is set to p/u/b (wake on any physical activity, unicast, or broadcast) instead of g. ethtool -s enp3s0 wol g narrows it to magic packets only. This is a genuinely common one on boards where the default is wol ug.
Wakes instantly then powers straight back off. Insufficient standby power on an old PSU, or a board that needs xhci_hcd unbound before suspend. The kernel log after the failed wake usually names the culprit.
Everything is right and nothing happens. tcpdump on the target’s segment. Either the packet arrives or it does not, and that single fact halves the search space.
Is it worth it?
For any machine that draws over about 50W and is idle most of the day: obviously yes. The setup is an afternoon and the payback is measured in months. Whether your machines are in that category is a question worth answering with a meter rather than a guess, and the answer is the whole subject of the mini PC versus old server power maths.
For a mini PC idling at 8W: no. You would save under twenty pounds a year and add a failure mode to every automation that assumes the machine is there. Leave it on.
For anything with users other than you: think carefully. “Why does the first thing I click always take ninety seconds?” is a support burden, and the honest answer is “to save four pounds a month”, which does not land well in a shared flat.
The protocol is genuinely bad. It is unauthenticated, it is broken by half the features on a modern network, and diagnosing it means understanding broadcast domains at a level nobody wants to. It also converts a 120W always-on machine into a 15W mostly-off one for the price of one script and a .link file, and after nearly thirty years nothing has replaced it. Some things are ugly and correct.




