Turning an Old Windows NUC into a Headless Ubuntu Server, with Reliable Wake-on-LAN
I had a small Intel NUC that started life running Windows, then got Ubuntu Desktop installed with a full GUI, and eventually became a headless media/file server: no monitor, no keyboard, just SSH and Samba shares. Along the way I hit a decent number of issues, some obvious, some that only showed up days later. This ended up long enough to split into three posts, each usable on its own, but building on each other in this order.
1. Converting Ubuntu Desktop to a Headless Server

TL;DR:
- Set the boot target to
multi-user.targetand disable/stop GDM so the machine boots to a text console instead of a desktop. - Know what actually breaks: GNOME’s built-in Remote Desktop has nothing to share with no session running, the login keyring becomes unreachable, audio is irrelevant. System services (SSH, Samba, NetworkManager, WOL) all keep working untouched.
- An NTFS drive left over from Windows had been auto-mounting itself at
/run/media/<user>/Mediaonly when someone logged into the desktop, audisks2feature. With no GUI session ever starting again, it just silently stopped mounting. Replaced with a proper/etc/fstabentry using thentfs3kernel driver, mounted permanently at/mnt/storage. - Sidebar: a DNS-over-TLS mismatch between the router and
systemd-resolvedwas breakingapt update, fixed by switching toopportunisticmode.
2. Reliable Wake-on-LAN with a HomeKit Switch, Behind a UniFi Firewall

TL;DR:
- Enable magic-packet WOL with
ethtool, then persist it through NetworkManager (nmcli connection modify ... wake-on-lan magic) so it survives reboots. - Two settings were silently blocking it: Energy-Efficient Ethernet negotiating a low-power link state and the USB wrongly armed as an ACPI wakeup source (causing spurious resumes)
- A fourth, sneakier one: EEE quietly re-enables itself after every suspend/resume cycle, since the oneshot service only runs at boot. Fixed with a
systemdsleep hook that reapplies the fix after every resume, not just at startup. - Added a HomeKit switch via Homebridge and
homebridge-cmd4(the olderhomebridge-cmdaccessoryplugin is abandoned and crashes on Homebridge v2). Hit a scripting bug where Cmd4 sends1/0rather than"true"/"false", which silently broke only the “turn on” direction. - Covers the UniFi firewall rules needed when the Homebridge controller lives on an isolated VLAN (SSH, WOL, and ICMP each need their own narrow allow rule), plus why a WOL broadcast packet needs to be sent directed at the target’s IP to cross subnets at all.
3. Setting up Auto-Suspend on Ubuntu to keep SMB Transfers, Torrents, and SSH Sessions alive

TL;DR:
- GNOME’s idle timer (even if it were running) only tracks keyboard/mouse/screen activity, useless for a headless file server. Used
autosuspendinstead, which suspends based on a configurable set of “checks.” - Configured
SmbandNetworkBandwidthchecks so the machine won’t sleep mid file-transfer. - An active SSH session wasn’t keeping the machine awake despite a
Userscheck being enabled. Root cause:/run/utmp, the filewhoreads session data from, didn’t exist on this system at all, likely a gap from its Desktop-install origins. Fixed by recreating it and adding asystemd-tmpfilesrule so it persists across reboots. - Extended it further for a headless torrent client: an
ActiveConnectioncheck keeps it awake while the web UI is open, and anExternalCommandcheck keeps it awake while a torrent is actively transferring, two genuinely different conditions.
I hope these three guides help you figure out any issues you might be facing if you’re also turning an old Windows NUC, or any Desktop-install box, into a proper headless Ubuntu server. Read together, they cover the whole loop I was trying to close: a properly headless machine with its storage mounted correctly, that wakes reliably when I need it and can be triggered from my phone, and sleeps on its own the rest of the time without ever cutting off something actually in progress.
I’ve since added one more piece: getting autosuspend to detect active Jellyfin streams and browsing, since none of the checks above catch someone just watching something.
If this post has been useful, support me by buying me a latte or two 🙂
