Building an All-in-One Home Server on Raspberry Pi 5

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

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.

  1. Download & Install: Download Raspberry Pi Imager from the official website.
  2. Select Device & OS:
  • Device: Raspberry Pi 5
  • OS: Raspberry Pi OS (64-bit) (Debian Bookworm)
  • Storage: Select your inserted MicroSD card.
  1. 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).
  • 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 named Raspberry-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.118

Or using mDNS hostname

ssh kenshiro@Raspberry-PI5.local

Step 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/portainer

4. 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.sh

Verification: Log out and reconnect via SSH, then verify Docker and Compose:

docker --version
docker compose version

5. 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/conf

Step 5.2: Start AdGuard Home

cd /opt/stacks/adguard && docker compose up -d

Step 5.3: Web Setup & DNS Rewrites

  • Navigate to http://192.168.1.118:8080 in 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 eth0

Output 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:... or 2001:...): 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 -I to view all assigned IPs simultaneously.
  • Method 2: Via AdGuard Home Web UI:

Router Configuration Steps

  • Static IP Reservation:
    • Assign a fixed IPv4 address to your Raspberry Pi 5 MAC address (192.168.1.118).
  • 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.
  • 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.

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/wireguard

Start WireGuard-UI:

cd /opt/stacks/wireguard && docker compose up -d

Nginx 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/letsencrypt

Start Nginx Proxy Manager:

cd /opt/stacks/nginxproxymanager && docker compose up -d

Portainer 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:/data

Start Portainer:

cd /opt/stacks/portainer && docker compose up -d

Filebrowser (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 /srv

Initialize the database file and start Filebrowser:

sudo touch /opt/filebrowser/filebrowser.db
cd /opt/stacks/filebrowser && docker compose up -d

Homepage (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:ro

Start Homepage:

cd /opt/stacks/homepage && docker compose up -d

8. 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.target

Enable and start the service:

sudo systemctl daemon-reload
sudo systemctl enable --now rclone-kopia

Step 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.target

Enable and start Kopia Server:

sudo systemctl daemon-reload
sudo systemctl enable --now kopia-server

Step 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/kenshiro

9. System Verification & Health Checks

Verify all endpoints and active service ports on your home server:

ServiceProtocol/AddressLocal Gateway PortPurpose
Nginxhttp://192.168.1.118:8181Reverse Proxy Router
AdGuardhttp://192.168.1.118:808053 / 8080DNS Filtering & Local Rewrites
Portainerhttp://192.168.1.118:90009000Docker Web Interface
Filebrowserhttp://192.168.1.118:80828082File Manager
Homepage Dashboardhttp://192.168.1.118:30003000Unified UI Portal
Rclonehttp://127.0.0.1:80858085Cloud 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:

  1. 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).
  2. 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 *.home to route all local subdomains).
    • IP Address: 192.168.1.118
  • 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).
  • 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.

Share with...