Tuning a tiny Linux home server: notes that survived a year
· 2 min read·Linux
The handful of sysctl, systemd and filesystem tweaks that actually mattered on a low-power home server, and the ones I reverted.
A year ago I replaced a noisy old desktop with a fanless mini PC as the home server. It runs a few containers, backups and the reverse proxy for everything else. These are the tweaks that are still in place twelve months later, and the ones that quietly disappeared.
Power budget
The server idles at about 6 W. That number is the whole reason the old desktop had to go, so it is worth being explicit about what adds up to it.
| Component | Idle | Under load |
|---|---|---|
| Board and CPU | 4 W | 18 W |
| NVMe system disk | 1 W | 3 W |
| 2.5 GbE NIC | 1 W | 2 W |
Kernel settings that stayed
Most of the sysctl folklore made no measurable difference. Two settings did.
# /etc/sysctl.d/90-homeserver.conf
vm.swappiness = 10
vm.dirty_background_ratio = 5
vm.dirty_ratio = 20
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr- Lower swappiness stopped the box from paging out the containers overnight when the backup job filled the page cache.
- BBR with fq made the off-site backup upload roughly twice as fast over the asymmetric home connection.
systemd units instead of cron
Every periodic job is a service plus a timer. The unit below runs the backup and gets killed if it hangs, which cron never did for me.
[Unit]
Description=Nightly restic backup
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
TimeoutStartSec=2h
Nice=10
IOSchedulingClass=idleThe matching timer uses Persistent=true so a missed run happens at the next boot instead of being skipped.
A tiny watchdog
The only monitoring is a script that posts a message when a service has been failed for more than a few minutes. It is short enough to show in full.
import subprocess
import sys
failed = subprocess.run(
["systemctl", "--failed", "--no-legend", "--plain"],
capture_output=True,
text=True,
check=False,
).stdout.split()
units = [line for line in failed if line.endswith(".service")]
if units:
print("failed units:", ", ".join(units))
sys.exit(1)The best monitoring is the kind you will not disable after the third false alarm.
What I reverted
- Custom I/O schedulers. The default mq-deadline on NVMe was already fine, and the change made one of the disks noticeably slower.
- Transparent huge pages set to always. Memory usage went up, nothing got faster.
- A hand-tuned ZFS ARC limit. Reverting to the default stopped a weekly slowdown that I had been blaming on the containers.
If there is one takeaway: measure before and after, write the number down next to the change, and revert anything that did not move it.
Comments 0
No comments yet.
Sign in with GitHub to join the discussion.
Only your public profile is used.