Pi-hole, Minecraft, WebDAV and a git mirror on a 2009 laptop with 4GB memory and tailnet only access.
Shell
2
19 commits
updated Sep 21, 2026
A home server built from a 2009 Toshiba laptop. It runs Pi-hole, a Minecraft server, a small WebDAV share for my phone, and a git mirror for my notes.
Mostly notes for myself, but it should help anyone at my level getting into homelabbing. Everything here matches what's actually running on the box.
This repo is the base: the hardware, the network, security, Pi-hole and backups. The other services each have their own repo:
Anything I add later gets the
bobserver
topic, so that link always lists all of them.
laptop ─┐
phone ──┼── tailnet (WireGuard mesh) ── server ─┬─ Minecraft :25565
friend ──┘ ├─ WebDAV :8443
└─ git mirror (outbound)
LAN ─────────────────────────────────────── Pi-hole :53
| Service | What it does | RAM used | Who can reach it |
|---|---|---|---|
| Minecraft (Paper) | Game server for two players | 2.2 GB | Tailnet only |
| Docker | Installed for later, nothing running | 164 MB | — |
| Tailscale | Private network between my devices | 110 MB | — |
| Pi-hole | Blocks ads and bad domains for the whole house | 86 MB | Home network |
| fail2ban | Bans IPs that keep failing SSH logins | 37 MB | — |
| Apache + WebDAV | Lets my phone drop notes onto the server | 12 MB | Tailnet only |
| git mirror | Syncs my notes repo every 5 minutes | tiny | Outbound only |
Total: about 2.6GB used out of 3.8GB. Measured with systemd-cgtop -m.
I didn't forward a single port on my router. Instead, every device I own runs Tailscale. Tailscale builds a private, encrypted network between them (a "tailnet"), so my laptop and phone can reach the server from anywhere.
My friend gets in the same way. I shared the server with their Tailscale account. They don't get an account on the server itself.
Who can reach what:
Paper on Java 25, run by systemd, tailnet only. The service file, backups, setup and everything that went wrong with it are in mc-server.
DNS turns names like google.com into addresses. Pi-hole sits in the
middle. When a device asks for an ad or malware domain, Pi-hole answers
0.0.0.0, which leads nowhere, so the ad never loads.
Files: scripts/setup-pihole.sh, config/pihole-blocklists.txt,
config/pihole-v6-settings.txt
My phone drops notes onto the server over WebDAV. A script moves them into my notes repo every 5 minutes, and my laptop pulls them from GitHub. All of it is in org-sync.
backup-runbackup-run runs every script in ~/.config/backup.d/. Each service
links its own script in there, so adding a service never means
editing this repo~/.local/state/backup-run.logThe notes don't need this. They're a git repo, so every sync is already a backup with full history.
Files: scripts/backup-run, crontab.example
unattended-upgrades installs security patches on its
ownlogind.conf)Files: config/sshd-hardening.conf, scripts/ufw-rules.sh
Every service repo follows the same pattern, so a new one never touches this repo:
install.sh that's safe to run twicetailscale0 only~/.config/backup.d/sudo ss -tlnp and free -h before adding onebobserver topic on GitHubProblems with Minecraft and WebDAV are written up in their own repos.
My hardening file (99-hardening.conf) said PasswordAuthentication no. But
sudo sshd -T, which shows the settings SSH is really using, said
yes.
The Ubuntu installer had made a file called 50-cloud-init.conf that
said yes. SSH reads the files in that folder in alphabetical order
and keeps the first value it finds. 50 comes before 99, so my file
lost. I renamed mine to 00-hardening.conf.
When I compared sudo ufw status to this README, three rules were
wider than I'd written down. One let every tailnet device reach every
port. Others opened SSH, DNS and HTTP to anyone, including over IPv6.
The server has public IPv6 addresses, so the router was the only thing
stopping outside traffic. I replaced them with narrower rules.
sudo ufw status numbered # firewall rules
sudo install -d /run/sshd; sudo sshd -T | grep password # should say "no"
sudo ss -tlnp # which program uses which port
pihole status # is ad blocking on?
tail ~/.local/state/backup-run.log # did last night's backups work?
free -h # RAM use
SSH here starts on demand (ssh.socket), so /run/sshd often doesn't
exist, and sshd -T refuses to run without it. Hence the install -d
first.
config/ SSH hardening, Pi-hole blocklists and settings
docs/ SETUP.md, step-by-step setup in the right order
scripts/ backup-run, setup-pihole.sh, ufw-rules.sh
crontab.example
Start with docs/SETUP.md. Order matters: firewall before services,
Tailscale before anything that should only be reachable over the
tailnet, fixed IP before Pi-hole.
doodoo, a terminal TODO manager in C++ with ncurses.
Shell
100.0%
Pi-hole, Minecraft, WebDAV and a git mirror on a 2009 laptop with 4GB memory and tailnet only access.
Shell
2
19 commits
updated Sep 21, 2026
A home server built from a 2009 Toshiba laptop. It runs Pi-hole, a Minecraft server, a small WebDAV share for my phone, and a git mirror for my notes.
Mostly notes for myself, but it should help anyone at my level getting into homelabbing. Everything here matches what's actually running on the box.
This repo is the base: the hardware, the network, security, Pi-hole and backups. The other services each have their own repo:
Anything I add later gets the
bobserver
topic, so that link always lists all of them.
laptop ─┐
phone ──┼── tailnet (WireGuard mesh) ── server ─┬─ Minecraft :25565
friend ──┘ ├─ WebDAV :8443
└─ git mirror (outbound)
LAN ─────────────────────────────────────── Pi-hole :53
| Service | What it does | RAM used | Who can reach it |
|---|---|---|---|
| Minecraft (Paper) | Game server for two players | 2.2 GB | Tailnet only |
| Docker | Installed for later, nothing running | 164 MB | — |
| Tailscale | Private network between my devices | 110 MB | — |
| Pi-hole | Blocks ads and bad domains for the whole house | 86 MB | Home network |
| fail2ban | Bans IPs that keep failing SSH logins | 37 MB | — |
| Apache + WebDAV | Lets my phone drop notes onto the server | 12 MB | Tailnet only |
| git mirror | Syncs my notes repo every 5 minutes | tiny | Outbound only |
Total: about 2.6GB used out of 3.8GB. Measured with systemd-cgtop -m.
I didn't forward a single port on my router. Instead, every device I own runs Tailscale. Tailscale builds a private, encrypted network between them (a "tailnet"), so my laptop and phone can reach the server from anywhere.
My friend gets in the same way. I shared the server with their Tailscale account. They don't get an account on the server itself.
Who can reach what:
Paper on Java 25, run by systemd, tailnet only. The service file, backups, setup and everything that went wrong with it are in mc-server.
DNS turns names like google.com into addresses. Pi-hole sits in the
middle. When a device asks for an ad or malware domain, Pi-hole answers
0.0.0.0, which leads nowhere, so the ad never loads.
Files: scripts/setup-pihole.sh, config/pihole-blocklists.txt,
config/pihole-v6-settings.txt
My phone drops notes onto the server over WebDAV. A script moves them into my notes repo every 5 minutes, and my laptop pulls them from GitHub. All of it is in org-sync.
backup-runbackup-run runs every script in ~/.config/backup.d/. Each service
links its own script in there, so adding a service never means
editing this repo~/.local/state/backup-run.logThe notes don't need this. They're a git repo, so every sync is already a backup with full history.
Files: scripts/backup-run, crontab.example
unattended-upgrades installs security patches on its
ownlogind.conf)Files: config/sshd-hardening.conf, scripts/ufw-rules.sh
Every service repo follows the same pattern, so a new one never touches this repo:
install.sh that's safe to run twicetailscale0 only~/.config/backup.d/sudo ss -tlnp and free -h before adding onebobserver topic on GitHubProblems with Minecraft and WebDAV are written up in their own repos.
My hardening file (99-hardening.conf) said PasswordAuthentication no. But
sudo sshd -T, which shows the settings SSH is really using, said
yes.
The Ubuntu installer had made a file called 50-cloud-init.conf that
said yes. SSH reads the files in that folder in alphabetical order
and keeps the first value it finds. 50 comes before 99, so my file
lost. I renamed mine to 00-hardening.conf.
When I compared sudo ufw status to this README, three rules were
wider than I'd written down. One let every tailnet device reach every
port. Others opened SSH, DNS and HTTP to anyone, including over IPv6.
The server has public IPv6 addresses, so the router was the only thing
stopping outside traffic. I replaced them with narrower rules.
sudo ufw status numbered # firewall rules
sudo install -d /run/sshd; sudo sshd -T | grep password # should say "no"
sudo ss -tlnp # which program uses which port
pihole status # is ad blocking on?
tail ~/.local/state/backup-run.log # did last night's backups work?
free -h # RAM use
SSH here starts on demand (ssh.socket), so /run/sshd often doesn't
exist, and sshd -T refuses to run without it. Hence the install -d
first.
config/ SSH hardening, Pi-hole blocklists and settings
docs/ SETUP.md, step-by-step setup in the right order
scripts/ backup-run, setup-pihole.sh, ufw-rules.sh
crontab.example
Start with docs/SETUP.md. Order matters: firewall before services,
Tailscale before anything that should only be reachable over the
tailnet, fixed IP before Pi-hole.
doodoo, a terminal TODO manager in C++ with ncurses.
Shell
100.0%