How-To Guides

How I set it up

Write-ups of things I’ve figured out, so you don’t have to. Commands are for Debian-based systems (Raspberry Pi OS, Ubuntu, Linux Mint) unless noted.

Beginner~30 minRaspberry Pi

Host a website on a Raspberry Pi

A static site (plain HTML, CSS and JS) is the easiest thing to self-host: there’s no database and almost nothing to attack. Caddy is a web server that gets and renews HTTPS certificates for you automatically.

1. Install Caddy

sudo apt update
sudo apt install -y caddy
caddy version

2. Copy your site over

sudo mkdir -p /var/www/site
# from your computer:
rsync -av --delete ./dist/ pi@<pi-address>:/tmp/site/
# on the Pi:
sudo rsync -av --delete /tmp/site/ /var/www/site/

3. Point Caddy at it

Edit /etc/caddy/Caddyfile. Before you have a domain, serve on port 80 on your LAN:

:80 {
    root * /var/www/site
    encode zstd gzip
    file_server
}

Once you own a domain and its DNS points at you, replace :80 with the domain name. Caddy will fetch a certificate on its own.

sudo systemctl reload caddy

4. Put it on the internet

Two common options:

  • Cloudflare Tunnel. The Pi makes an outbound connection to Cloudflare, so you don’t open any ports and your home IP stays hidden.
  • Port forwarding. Forward TCP 80 and 443 on your router to the Pi and point the domain’s A record at your public IP. Simple, but your home IP is public.
Intermediate~45 minPrivacy

Pi-hole + Unbound

Pi-hole blocks ads and trackers for every device on your network by refusing to look up their domains. Unbound turns your server into its own recursive DNS resolver, so lookups go straight to the authoritative servers instead of through Google or Cloudflare.

1. Install Pi-hole

curl -sSL https://install.pi-hole.net | bash

2. Install Unbound

sudo apt install -y unbound

Create /etc/unbound/unbound.conf.d/pi-hole.conf:

server:
    verbosity: 0
    interface: 127.0.0.1
    port: 5335
    do-ip4: yes
    do-udp: yes
    do-tcp: yes
    do-ip6: no
    harden-glue: yes
    harden-dnssec-stripped: yes
    use-caps-for-id: no
    edns-buffer-size: 1232
    prefetch: yes
    private-address: 192.168.0.0/16
    private-address: 172.16.0.0/12
    private-address: 10.0.0.0/8
sudo systemctl restart unbound
dig pi-hole.net @127.0.0.1 -p 5335

3. Connect them

In the Pi-hole admin page under Settings → DNS, untick every upstream provider and add a custom upstream of 127.0.0.1#5335. Then set your router’s DHCP DNS server to the Pi-hole’s address.

Intermediate~45 minNetworking

WireGuard VPN server

A VPN back into your home network lets you reach your server, NAS and Pi-hole from anywhere. WireGuard is fast, modern, and has a small, easy-to-read config.

1. Install and make keys

sudo apt install -y wireguard
wg genkey | tee server.key | wg pubkey > server.pub
wg genkey | tee phone.key  | wg pubkey > phone.pub

2. Server config

/etc/wireguard/wg0.conf (replace eth0 with your LAN interface):

[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = <contents of server.key>
PostUp   = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

[Peer]
PublicKey = <contents of phone.pub>
AllowedIPs = 10.8.0.2/32

3. Turn it on

echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl --system
sudo systemctl enable --now wg-quick@wg0

Forward UDP 51820 on your router to the server. On the phone, create a tunnel with phone.key as its private key, address 10.8.0.2/32, DNS set to your Pi-hole, and the server’s public key and your public IP as the peer.

Keep *.key files private. Anyone with a private key can join your network.

Intermediate~30 minAI

Local AI with Ollama + Open WebUI

Run the model on the machine with the GPU, and the chat interface on a Pi so it’s always up.

1. On the GPU machine

curl -fsSL https://ollama.com/install.sh | sh
ollama pull qwen3-coder:30b
# let other machines on your LAN reach it:
sudo systemctl edit ollama
#   [Service]
#   Environment="OLLAMA_HOST=0.0.0.0"
sudo systemctl restart ollama

2. On the Pi

With Docker installed:

docker run -d --name open-webui --restart always \
  -p 3000:8080 \
  -e OLLAMA_BASE_URL=http://<gpu-machine-ip>:11434 \
  -v open-webui:/app/backend/data \
  ghcr.io/open-webui/open-webui:main

Open http://<pi-address>:3000 and create the admin account.

Don’t expose port 11434 to the internet. Ollama has no authentication. Reach it over your VPN instead.

AdvancedHardware

SSD + Bluetooth iPod mod

[Draft: parts list, opening the case without bending it, installing the storage adapter, battery swap, Bluetooth board and where it fits, restoring and loading music.]