Contents

Wake-on-LAN Automation: Powering Servers On and Off with Home Assistant

Let the power-hungry box sleep until something actually needs it

Contents

I have a beefy machine in the cupboard that exists to do exactly one thing: transcode media and crunch the occasional batch job. It pulls something like 90 watts at idle, which over a year is a meaningful slice of the electricity bill for a box that is genuinely busy maybe two hours a day. For ages I left it running because the alternative — getting up and pressing the power button when I wanted to watch something — was worse. Then I wired it into Home Assistant, and now it sleeps until it is needed and shuts itself down when it is idle. The savings paid for the effort in a couple of months.

That “couple of months” is the interesting number. 90 watts of idle draw, running around the clock for the twenty-two hours a day it does nothing, is roughly 60 kWh a month of pure waste. At typical UK electricity prices that is real money leaving your account to keep a fan spinning over a machine nobody is using. Automating it away is one of the few homelab projects with a payback you can point at on a bill — the same logic I got into in the real cost of self-hosting, where the electricity almost always dwarfs the hardware.

Wake-on-LAN (WoL) is the magic that makes this possible. A network card, even when the machine is “off” (technically in S5 soft-off), keeps a trickle of power to the NIC and listens for a special “magic packet” addressed to its MAC. Receive that packet, and the board powers on. Home Assistant can send it on a schedule, on a button press, or — the good bit — automatically when something on the network tries to reach the sleeping server.

Step one: actually enable WoL on the hardware

Advertisement

This is where most people fail, so do it first. Two places need to agree, and if either one is wrong, nothing else in this post will work.

  1. The BIOS/UEFI. Find the setting usually called “Wake on LAN”, “Power On by PCI-E”, or “ErP” (which you want disabled — ErP/EuP energy-saving mode cuts power to the NIC entirely when the machine is off and kills WoL stone dead). Enable wake, disable ErP. This is the number-one reason people’s magic packets vanish into the void: the NIC has no standby power to hear them.
  2. The OS. On Linux, the network driver often disables WoL on shutdown to save that trickle of power. Check and set it with ethtool:
1
2
3
4
5
6
$ sudo ethtool eno1 | grep Wake
        Supports Wake-on: pumbg
        Wake-on: d                 # 'd' = disabled, this is the problem
$ sudo ethtool -s eno1 wol g       # 'g' = wake on magic packet
$ sudo ethtool eno1 | grep Wake-on
        Wake-on: g

The Supports Wake-on: pumbg line is worth reading: it lists the wake modes the NIC is capable of, and g (magic packet) needs to be among them. If it isn’t, this card can’t do what you want and no amount of config will change that — but almost every modern onboard NIC supports g.

Because that setting resets on reboot, persist it with a systemd unit:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# /etc/systemd/system/wol.service
[Unit]
Description=Enable Wake-on-LAN
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/sbin/ethtool -s eno1 wol g

[Install]
WantedBy=multi-user.target

Then sudo systemctl enable --now wol.service. Note the MAC of eno1 — you’ll need it in a moment. This unit is the single most important part of the whole setup: it is what stops WoL from silently breaking the next time you reboot the machine.

Step two: the Home Assistant side

Add the Wake-on-LAN integration and define a switch in configuration.yaml. The clever part is giving the switch a turn-off action too — Home Assistant can’t magic-packet a machine off, so we SSH in and tell it to suspend or shut down:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
switch:
  - platform: wake_on_lan
    name: Transcode Box
    mac: "a4:bb:6d:11:22:33"
    host: 192.168.1.50          # used to ping for on/off state
    turn_off:
      service: shell_command.suspend_transcode_box

shell_command:
  suspend_transcode_box: >
    ssh -i /config/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new
    [email protected] 'sudo systemctl suspend'

The host field lets Home Assistant ping the box to know whether it is actually up, so the switch reflects real state rather than just “I sent a packet and hoped.” That feedback loop matters — without it, the switch lies to you, and automations built on a lying switch behave unpredictably.

Note the SSH key type: id_ed25519, not RSA. There is no reason to generate RSA keys for new setups any more; ssh-keygen -t ed25519 gives you a shorter, faster, and at least as secure key. Give the ha-control user a tightly scoped sudoers entry — only systemctl suspend, nothing else — so a compromised Home Assistant can’t do more than nap your server. This is a small thing that costs nothing and closes a real hole: a home-automation box is a soft target, and you do not want the key it holds to be a passport to root on your server.

Step three: wake it automatically when something needs it

Advertisement

A button is fine, but the real prize is making the box wake itself when demand appears, with no human in the loop. The pattern I use: a proxy that, on receiving a request for a service on the sleeping box, wakes it, waits for it to come up, then forwards the request. For a media server, you can approximate this entirely in Home Assistant with an automation triggered by, say, your TV turning on:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
automation:
  - alias: "Wake transcode box when the living room TV turns on"
    trigger:
      - platform: state
        entity_id: media_player.living_room_tv
        to: "on"
    condition:
      - condition: state
        entity_id: switch.transcode_box
        state: "off"
    action:
      - service: switch.turn_on
        target:
          entity_id: switch.transcode_box

  - alias: "Sleep transcode box if idle for an hour"
    trigger:
      - platform: state
        entity_id: binary_sensor.transcode_box_active
        to: "off"
        for: "01:00:00"
    action:
      - service: switch.turn_off
        target:
          entity_id: switch.transcode_box

That binary_sensor.transcode_box_active is whatever signals “real work is happening” — an active Jellyfin session, a command-line job, CPU above a threshold. I derive mine from the media server’s API. When it has been quiet for an hour, the box suspends itself. When the telly comes on, it is awake by the time the app finishes loading. The one-hour idle window is deliberately generous: sleeping too aggressively means the box thrashes awake and asleep, which wears on the hardware and annoys everyone. Better to waste a little idle time than to bounce the machine every ten minutes.

If you want to get properly clever, the same trigger philosophy plays nicely with the rest of a Home Assistant estate — pair it with power monitoring and you can watch, in real numbers, how much the automation is actually saving you. Seeing the draw drop to near-zero on the dashboard is oddly satisfying, and it is the proof that the whole exercise was worth it.

The proper way: a wake-on-demand proxy

The TV-triggered automation is a good approximation, but it is a proxy for the thing you actually want, which is: any client that tries to reach a service on the sleeping box causes it to wake, transparently. Home Assistant is a blunt instrument for this because it only knows about the triggers you have wired up. The cleaner pattern lives on the network itself.

The classic implementation is a small forwarding proxy that sits on an always-on machine and listens on the port your service normally uses. When a connection arrives, the proxy fires the magic packet, waits for the box to answer a ping, then forwards the connection through. To the client it is invisible — the request just takes a few seconds longer the first time, while the server boots. On Linux you can build this with systemd-socket-proxyd, or with a purpose-built tool that wraps the wake-and-wait logic:

1
2
3
4
$ # pseudo-flow the proxy runs on each incoming connection:
$ wakeonlan a4:bb:6d:11:22:33      # send the magic packet
$ until ping -c1 -W1 192.168.1.50 >/dev/null 2>&1; do sleep 1; done
$ # box is up — forward the client's connection to it

This is more robust than a Home Assistant automation because it does not depend on you having predicted the trigger. Anything that reaches for the service wakes the box. The trade-off is that the proxy host must always be on — but that is usually some tiny, low-draw machine you already leave running, so the arithmetic still comes out miles ahead of leaving the 90-watt box awake.

The savings, in actual numbers

It is worth doing the sum, because “it saves money” is easy to say and easy to overstate. A machine drawing 90 watts continuously uses about 65 kWh a month. Suspend it for the twenty-two hours a day it does nothing, and you cut that to roughly a tenth. At a typical UK unit rate that is the difference between a noticeable line item on the bill and a rounding error — tens of pounds a year, recurring, for an evening’s work. That payback is why this project sits near the top of my list for anyone with a high-draw box that is mostly idle.

Troubleshooting: when the packet goes nowhere

The magic packet sends but the box never wakes. Work backwards. First, is ErP disabled in the BIOS? If the NIC has no standby power it cannot hear anything. Second, did ethtool revert on the last shutdown? Boot the machine manually, check Wake-on: g, and confirm the systemd unit is enabled — this is the single most common cause of “it worked yesterday.”

It wakes but the OS doesn’t come up. Almost always the ethtool setting reverting on the previous shutdown. The systemd unit above fixes this for good, but it only takes effect once you have booted with it enabled at least once.

WoL works from one machine but not another. WoL packets are layer-2 broadcasts, so they do not cross subnets or VLANs without help. Keep Home Assistant and the target on the same broadcast domain, or configure a directed broadcast on your router. If you have segmented your network — and if you run a homelab you probably should have — this is the usual culprit; the packet is being dropped at the segment boundary.

Wi-Fi wakes are flaky. Some access points and power-saving Wi-Fi modes drop the magic packet. Wired is dramatically more reliable for the target machine. If the box you want to wake is on Wi-Fi, consider running a cable to it — this is one case where wireless simply is not worth the intermittent failures.

The switch shows “off” while the box is clearly on. The host ping is being blocked, usually by the target’s firewall. Allow ICMP echo from Home Assistant’s IP, or the state feedback will be permanently wrong.

Verdict

Worth it? If you have a high-draw machine that is idle most of the day, absolutely — this is one of the few homelab projects with a tangible, recurring payoff in pounds off the bill, not just satisfaction. The setup is an evening, the hardware-enablement is the only genuinely fiddly part, and once it works it is invisible: the box is simply there when you need it and quietly asleep when you don’t.

If your server is genuinely busy around the clock, skip all this — waking and sleeping a machine that never idles just adds moving parts for no gain. But most homelab boxes aren’t busy round the clock. They spend most of their lives burning electricity to do nothing, and they should be sleeping far more than they are.

Advertisement
Advertisement
Smarc
Written by Smarc

Founder and editor of vo.rs. A lifelong tinkerer who self-hosts far more than is sensible, hardens Linux boxes for fun, and prods the latest AI tools to see what they can really do. The how-to guides here are the notes Smarc wishes had existed the first time round.