I am writing this guide for my future self, for the next time he inadvertently destroys the Raspberry Pi’s SD card.!
This is a simple deep-dive on turning a Raspberry Pi 5 into a secure home server. In this guide, we will transform a raw micro-SD card into a self-hosted infrastructure featuring network-wide ad-blocking (AdGuard Home), private VPN access (WireGuard), reverse proxy routing (Nginx Proxy Manager), root-level file management (Filebrowser), centralized Docker administration (Portainer), automated encrypted cloud backups (Kopia + Rclone), and a clean unified dashboard (Homepage).
Table of Contents
- 1. Hardware & Prerequisites
- 2. MicroSD Preparation with Raspberry Pi Imager
- 3. Initial System Setup & Network Discovery
- 4. Docker & Docker Compose Installation
- 5. Deploying AdGuard Home (DNS & Ad-Blocking)
- 6. Router Configuration & Networking
- 7. Deploying Core Infrastructure Services
- 8. Automated Offsite Backup Engine (Kopia + Rclone)
- 9. System Verification & Health Checks
- 10. Local Domain Routing (AdGuard Home & Nginx Proxy Manager Integration)
1. Hardware & Prerequisites
Before starting, ensure you have the following hardware:
- Single Board Computer: Raspberry Pi 5 (8GB RAM recommended).
- Storage: High-speed A2-rated MicroSD card (64GB+) or NVMe M.2 SSD with PCIe HAT.
- Power Supply: Official 27W USB-C Power Supply.
- Network Connection: Ethernet cable connected directly to your primary router.
2. MicroSD Preparation with Raspberry Pi Imager
We will flash Raspberry Pi OS (64-bit) using the official Raspberry Pi Imager to pre-configure SSH and user credentials headlessly.
- Download & Install: Download Raspberry Pi Imager from the official website.
- Select Device & OS:
- Device: Raspberry Pi 5
- OS: Raspberry Pi OS (64-bit) (Debian Bookworm)
- Storage: Select your inserted MicroSD card.
- Configure OS Customization Settings:
- Click EDIT SETTINGS:
- General Tab:
- Set hostname:
Raspberry-PI5 - Set username:
kenshiro - Set password: (Enter a secure password)
- Configure Wireless LAN (optional, Ethernet preferred).
- Set Locale Settings (Time zone and Keyboard layout).
- Set hostname:
- Services Tab:
- Check Enable SSH → Select Use password authentication.
- Click SAVE and proceed to flash the MicroSD card.
3. Initial System Setup & Network Discovery
Insert the MicroSD card into your Raspberry Pi 5, attach the Ethernet cable, and power it on.
Step 3.1: Locating Your Raspberry Pi’s Local IP Address
Before establishing the first SSH connection, locate the local IPv4 address assigned to your Raspberry Pi by your router’s DHCP server:
- Method 1: Router Admin Interface (Recommended):
Log in to your router’s web interface (e.g.,http://192.168.1.1). Navigate to Connected Devices or DHCP Client List and locate the device namedRaspberry-PI5. - Method 2: Hostname Resolution (mDNS):
On macOS, Linux, or Windows (with Bonjour/mDNS enabled), open your terminal and ping the hostname directly:
ping Raspberry-PI5.local- Method 3: Network Scanning:
Use a mobile network scanner app (e.g., Fing) or run a command-line utility on your local network:
arp -a | grep -i "Raspberry"Step 3.2: Connect via SSH
Once you have retrieved the IP address (e.g., 192.168.1.118), connect via SSH using the username kenshiro:
ssh kenshiro@192.168.1.118Or using mDNS hostname
ssh kenshiro@Raspberry-PI5.localStep 3.3: System Update & Directory Workspace
Perform a full upgrade of Debian packages and establish a clean /opt workspace structure:
# Update system packages
sudo apt update && sudo apt full-upgrade -y
sudo apt autoremove -y
# Create stack and application volume directories
sudo mkdir -p /opt/stacks/{adguard,wireguard,nginxproxymanager,portainer,filebrowser,homepage}
sudo mkdir -p /opt/{adguard/conf,adguard/work,filebrowser,portainer/data}
sudo chown -R kenshiro:kenshiro /opt/stacks /opt/adguard /opt/filebrowser /opt/portainer4. Docker & Docker Compose Installation
Install Docker Engine using the official installation script:
# Download and run official Docker installer
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
# Add user 'kenshiro' to docker group to run containers without sudo
sudo usermod -aG docker kenshiro
# Enable and start Docker service
sudo systemctl enable --now docker
# Remove installation script
rm get-docker.shVerification: Log out and reconnect via SSH, then verify Docker and Compose:
docker --version
docker compose version5. Deploying AdGuard Home (DNS & Ad-Blocking)
AdGuard Home is deployed first so that local DNS filtering and custom domain rewrites are active before delegating router DNS traffic.
Step 5.1: Create Docker Compose Configuration
Create /opt/stacks/adguard/docker-compose.yml:
services:
adguardhome:
image: adguard/adguardhome:latest
container_name: adguardhome
restart: unless-stopped
ports:
- "53:53/tcp"
- "53:53/udp"
- "8080:8080/tcp" # Web UI mapped to 8080 (frees Port 3000 for Homepage)
volumes:
- /opt/adguard/work:/opt/adguardhome/work
- /opt/adguard/conf:/opt/adguardhome/confStep 5.2: Start AdGuard Home
cd /opt/stacks/adguard && docker compose up -dStep 5.3: Web Setup & DNS Rewrites
- Navigate to
http://192.168.1.118:8080in your web browser. - Complete the setup wizard (set admin web port to
8080). - Configure DNS Rewrites:
Go to Filters -> DNS Rewrites and add:files.home→192.168.1.118*(Local subdomain routing, see at the end).
6. Router Configuration & Networking
With AdGuard Home active, configure your primary router (192.168.1.1) to delegate all DNS queries to the Raspberry Pi.
How to Find Your Raspberry Pi’s IPv6 Address
- Method 1: Via Terminal (Recommended):
Run the following command on the Raspberry Pi:
ip -6 addr show dev eth0Output snippet:
inet6 2a02:1210:2033:2d00:8ba2:9efe:fe96:f29e/64 scope global dynamic
inet6 fe80::8ba2:9efe:fe96:f29e/64 scope link- Global Unicast Address (
2a02:...or2001:...): Public IPv6 assigned dynamically by your ISP. - Link-Local Address (
fe80::...): Static local IPv6 address. Recommended for router setup because it never changes, even if your ISP updates your public IPv6 prefix. - Quick alternative: Run
hostname -Ito view all assigned IPs simultaneously.
- Method 2: Via AdGuard Home Web UI:
- Open
http://192.168.1.118:8080, navigate to Setup Guide, and locate the detected IPv4 and IPv6 addresses.
- Open
Router Configuration Steps
- Static IP Reservation:
- Assign a fixed IPv4 address to your Raspberry Pi 5 MAC address (
192.168.1.118).
- Assign a fixed IPv4 address to your Raspberry Pi 5 MAC address (
- Router DNS Delegation (IPv4 & IPv6):
- Primary IPv4 DNS: Set to
192.168.1.118. - Primary IPv6 DNS (DHCPv6): Set “DNS Server IPv6 notified through DHCPv6” to your Raspberry Pi’s Link-Local address (
fe80::8aa2:9efe:fe96:f29e). - Why IPv6 DNS is critical: Notifying AdGuard’s IPv6 address ensures all network devices resolve DNS via AdGuard regardless of connection protocol.
- Primary IPv4 DNS: Set to
- Port Forwarding (Remote Access via WireGuard):
- WireGuard VPN: Forward External UDP Port
51820→192.168.1.118:51820. - Ports 80 & 443 (HTTP/HTTPS): Keep ports 80 and 443 closed to the Internet. Following a “VPN-First” security architecture, Nginx Proxy Manager handles reverse proxying internally. Remote access to web services is securely tunneled through WireGuard, keeping your server hidden from public port scanners.
- WireGuard VPN: Forward External UDP Port
7. Deploying Core Infrastructure Services
WireGuard & WireGuard-UI (Secure Remote Access)
Create /opt/stacks/wireguard/docker-compose.yml:
services:
wireguard-ui:
image: ngoduykhanh/wireguard-ui:latest
container_name: wireguard-ui
restart: unless-stopped
cap_add:
- NET_ADMIN
network_mode: host
environment:
- WGUI_SERVER_POST_UP_SCRIPT=iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
- WGUI_SERVER_POST_DOWN_SCRIPT=iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
volumes:
- /opt/wireguard/db:/app/db
- /etc/wireguard:/etc/wireguardStart WireGuard-UI:
cd /opt/stacks/wireguard && docker compose up -dNginx Proxy Manager (Reverse Proxy)
Create /opt/stacks/nginxproxymanager/docker-compose.yml:
services:
app:
image: 'jc21/nginx-proxy-manager:latest'
container_name: nginxproxymanager
restart: unless-stopped
ports:
- '80:80'
- '81:81'
- '443:443'
volumes:
- /opt/nginxproxymanager/data:/data
- /opt/nginxproxymanager/letsencrypt:/etc/letsencryptStart Nginx Proxy Manager:
cd /opt/stacks/nginxproxymanager && docker compose up -dPortainer CE (Container Management)
Create /opt/stacks/portainer/docker-compose.yml:
services:
portainer:
image: portainer/portainer-ce:latest
container_name: portainer
restart: unless-stopped
security_opt:
- no-new-privileges:true
ports:
- "9000:9000"
- "9443:9443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /opt/portainer/data:/dataStart Portainer:
cd /opt/stacks/portainer && docker compose up -dFilebrowser (Full Filesystem Scope)
To grant Filebrowser full access to host system directories (/opt, /etc, /home), run the container with root permissions (user: "0:0"), map root / to /srv, and explicitly specify the listening address --address 0.0.0.0.
Create /opt/stacks/filebrowser/docker-compose.yml:
services:
filebrowser:
image: filebrowser/filebrowser:latest
container_name: filebrowser
restart: unless-stopped
user: "0:0"
ports:
- "8082:80"
volumes:
- /:/srv
- /opt/filebrowser/filebrowser.db:/database/filebrowser.db
command: --address 0.0.0.0 --port 80 --database /database/filebrowser.db --root /srvInitialize the database file and start Filebrowser:
sudo touch /opt/filebrowser/filebrowser.db
cd /opt/stacks/filebrowser && docker compose up -dHomepage (Unified Application Dashboard)
Create /opt/stacks/homepage/docker-compose.yml:
services:
homepage:
image: ghcr.io/gethomepage/homepage:latest
container_name: homepage
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- /opt/homepage/config:/app/config
- /var/run/docker.sock:/var/run/docker.sock:roStart Homepage:
cd /opt/stacks/homepage && docker compose up -d8. Automated Offsite Backup Engine (Kopia + Rclone)
To back up host directories (/opt, /etc, /home/kenshiro) securely to Google Drive, we run an Rclone WebDAV bridge paired with Kopia Server.
Step 8.1: Configure Rclone Remote
rclone config
# Name: gdrive
# Storage: Google Drive
# Complete interactive OAuth token authorization flow
Step 8.2: Systemd Unit for Rclone WebDAV Server
Create /etc/systemd/system/rclone-kopia.service:
[Unit]
Description=Rclone WebDAV Bridge for Kopia Backup
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=kenshiro
ExecStart=/usr/bin/rclone serve webdav gdrive: --addr 127.0.0.1:8085 --config /home/kenshiro/.config/rclone/rclone.conf --vfs-cache-mode full
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.targetEnable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable --now rclone-kopiaStep 8.3: Systemd Unit for Kopia Daemon
Create /etc/systemd/system/kopia-server.service:
[Unit]
Description=Kopia Backup Daemon
After=network-online.target rclone-kopia.service
Wants=rclone-kopia.service
[Service]
Type=simple
User=root
ExecStart=/usr/bin/kopia server start --insecure
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.targetEnable and start Kopia Server:
sudo systemctl daemon-reload
sudo systemctl enable --now kopia-serverStep 8.4: Set Retention & Automated Backup Schedules
# Set weekly backup policy for target host directories
sudo kopia policy set /opt /etc /home/kenshiro --snapshot-interval=168h --keep-weekly=4 --keep-monthly=12
# Trigger initial manual backup snapshot
sudo kopia snapshot create /opt /etc /home/kenshiro9. System Verification & Health Checks
Verify all endpoints and active service ports on your home server:
| Service | Protocol/Address | Local Gateway Port | Purpose |
| Nginx | http://192.168.1.118:81 | 81 | Reverse Proxy Router |
| AdGuard | http://192.168.1.118:8080 | 53 / 8080 | DNS Filtering & Local Rewrites |
| Portainer | http://192.168.1.118:9000 | 9000 | Docker Web Interface |
| Filebrowser | http://192.168.1.118:8082 | 8082 | File Manager |
| Homepage Dashboard | http://192.168.1.118:3000 | 3000 | Unified UI Portal |
| Rclone | http://127.0.0.1:8085 | 8085 | Cloud Backup Transport |
Check container health at any time using:
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"10. Local Domain Routing (AdGuard Home & Nginx Proxy Manager Integration)
To access your containerized services using friendly custom domain names (such as files.home or portainer.home) instead of remembering IP addresses and port numbers (192.168.1.118:8082), AdGuard Home and Nginx Proxy Manager work in tandem:
- AdGuard Home (DNS Level): Intercepts local domain requests (e.g.,
files.home) and resolves them to the Raspberry Pi’s local IP address (192.168.1.118). - Nginx Proxy Manager (Application Level): Listens on HTTP port
80, inspects the incoming host header (files.home), and reverse-proxies the traffic to the designated container’s port (e.g.,8082).
Step 1: Create DNS Rewrites in AdGuard Home
- Access the AdGuard Home web dashboard at
http://192.168.1.118:8080. - Navigate to Filters → DNS Rewrites.
- Click Add DNS rewrite and enter:
- Domain Name:
files.home(or a wildcard rule like*.hometo route all local subdomains). - IP Address:
192.168.1.118
- Domain Name:
- Click Save.
Step 2: Configure Proxy Hosts in Nginx Proxy Manager
- Access Nginx Proxy Manager at
http://192.168.1.118:81. - Log in using your administrator credentials.
- Go to Hosts → Proxy Hosts and click Add Proxy Host.
- In the Details tab, configure:
- Domain Names:
files.home(press Enter after typing) - Scheme:
http - Forward Hostname / IP:
192.168.1.118 - Forward Port:
8082(Filebrowser host port) - Check Block Common Exploits.
- Check Websockets Support (required for live file upload/download in Filebrowser).
- Domain Names:
- Click Save.
Step 3: Verification
Open a new browser tab and navigate to http://files.home. You will be seamlessly routed to Filebrowser without entering port :8082.