Self-hosting web services aligns with the philosophy of having more control over your infrastructure. However, it comes with challenges:
While Virtual Private Servers (VPS) are a convenient way to host web services, they can become costly, particularly when large amounts of storage are required. This goes against the self-hosting ethos of using local resources effectively.
Hosting services directly on a home network is difficult when your ISP provides a dynamic IP address, making it hard to maintain reliable access to your services. Dynamic DNS (DDNS) services can help but often come with recurring costs, especially as the number of services grows. Furthermore, relying on third-party DDNS services contradicts the goal of minimizing external dependencies.
To address the storage issue, I’ve set up a Raspberry Pi 5 fitted with a 1TB NVMe drive. This cost-effective solution provides ample storage and aligns with the self-hosting philosophy. Learn more about how I set it up in : Boot Raspberry Pi 5 from NVMe
The key to overcoming the dynamic IP issue is a combination of a VPS and WireGuard:
In this setup, the VPS does not need significant storage or processing power. It only functions as a lightweight gateway, making it inexpensive while solving the dynamic IP issue effectively.
example.com
<VPS_IP>
Set up an A record for your domain pointing to your VPS IP address:
example.com A <VPS_IP>
# On both VPS and RPi
sudo apt update
sudo apt install wireguard
# On VPS
wg genkey | tee server_private.key | wg pubkey > server_public.key
# On RPi
wg genkey | tee client_private.key | wg pubkey > client_public.key
/etc/wireguard/wg0.conf
:[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = <server_private_key>
SaveConfig = true
# Allow the Raspberry Pi client
[Peer]
PublicKey = <client_public_key>
AllowedIPs = 10.0.0.2/32
/etc/wireguard/wg0.conf
:[Interface]
Address = 10.0.0.2/24
PrivateKey = <client_private_key>
ListenPort = 51820
[Peer]
PublicKey = <server_public_key>
AllowedIPs = 10.0.0.0/24 # Only route the 10.0.0.0 network (your VPS)
Endpoint = <VPS_IP>:51820
PersistentKeepalive = 25
Clean up the keys.
# On both VPS and RPi
sudo systemctl start wg-quick@wg0
sudo systemctl enable wg-quick@wg0
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
I think you need to reboot the machines to apply the changes: sudo reboot
.
# On VPS
ping 10.0.0.2
# On RPi
ping 10.0.0.1
We will serve a simple web page on the Raspberry Pi using Caddy.
Create a www
directory and an index.html
file:
mkdir /www
cd /www
touch index.html
echo "Hello from Raspberry Pi!" > index.html
Create a Caddyfile:
:80 {
root * /usr/share/caddy
file_server browse
}
Create a Docker Compose file to run Caddy and serve the web page on port 4001:
services:
caddy_static:
image: caddy:2.9
container_name: caddy_static
ports:
- "4001:80"
volumes:
- ./www:/usr/share/caddy
- ./Caddyfile:/etc/caddy/Caddyfile:ro
Run the Caddy container:
docker-compose up -d
To access the web service on the Raspberry Pi through the VPS, set up a reverse proxy on the VPS. We will also use Caddy for this purpose.
Create a Caddyfile on the VPS:
example.com {
reverse_proxy 10.0.0.2:4001
tls {
on_demand
}
}
🖐️ Warning: The tls on_demand directive is used here to simplify the example. While it automates TLS certificate issuance for any domain pointing to your VPS, this approach can introduce security risks. For a production setup it is better to manually manage certificates with a trusted CA like Let’s Encrypt to ensure full control.
Create a Docker Compose file to run Caddy on the VPS:
services:
caddy:
image: caddy:2.9
container_name: caddy_reverse_proxy
network_mode: "host"
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
restart: unless-stopped
volumes:
caddy_data:
caddy_config:
Run the Caddy container:
docker-compose up -d
Do your due diligence to secure your setup. Here are some suggestions:
That’s it, folks! You now have a self-hosted web service accessible through a VPS and a Raspberry Pi, even with a dynamic IP address. This setup is cost-effective and aligns with the self-hosting philosophy (at least my interpretation of it). With this foundation, you can expand your setup to host additional services and applications.