Reverse Proxy for the Homelab
Last week, I finally got around to set up an nginx reverse proxy for my home lab. Because it turned out a bit more involved than I had anticipated, I decided to share my notes.
This has been a long-term item on the todo list. The main motivation being that it is to hard to remember all the ports I have configured on various devices, and that password-managers tend to suggest multiple logins (when ports are the only thing separating the urls).
An added requirement was that I wanted to get this working at home but also when vpn-ing in via tailscale.
Nginx via Docker
The first step was to create an nginx container. I used the following docker compose:
# docker compose
services:
nginx:
image: nginx:latest
container_name: nginx
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./snippets:/etc/nginx/snippets
ports:
- 80:80
- 443:443
The nginx.conf
looks roughly like this
# nginx config
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name portainer.tiny.pnet;
include /etc/nginx/snippets/proxy-defaults.conf;
location / {
proxy_pass http://192.168.0.42:9000;
}
}
server {
listen 80;
server_name plex.tiny.pnet;
include /etc/nginx/snippets/proxy-defaults.conf;
location / {
proxy_pass http://192.168.0.42:32400;
}
}
server {
listen 80 default_server;
server_name _;
return 404;
}
}
In the example, I want to reach my main home server as tiny
. It runs on 192.168.0.42
and here I only created routes for the portainer and plex frontends. More can be added using the same pattern. Devices with other addresses and server names work the same, but you will need to adapt the dns settings below if you want to use a different naming scheme. I wanted all devices to end with *.pnet
.
The referenced snippet proxy-defaults.conf
applies decent default settings and saves us a few copy-paster (via this blog post).
# snippets/proxy-defaults.conf
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_redirect off;
proxy_http_version 1.1;
# WebSocket support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
So far so good.
The reverse proxy should be running but we have no real way to check it yet.
DNS via Pi-hole
The next step is to make sure that the reverse proxy can be discovered on the local network. Unfortunately, I did not find a way to do this natively using just the fritzbox router (AVM has removed the option to configre local DNS long ago).
The next best better option is to use a Pi-hole, as it comes with a DNS server — and all my mobile devices that lack native adblockers use it anyway.
If you run an OpenWRT router, the procedure should be the similar.
In my case, the Pi-hole is just another docker container, running on the same machine. Note that I am running its web interface on port 8080
to keep 80
free for nginx.
For completeness:
# docker compose
services:
pihole:
container_name: pihole
image: pihole/pihole:latest
ports:
- "53:53/tcp"
- "53:53/udp"
- "8080:80/tcp"
dns:
- 127.0.0.1
- 8.8.8.8
volumes:
- '~/docker/pihole/etc-pihole:/etc/pihole'
- '~/docker/pihole/etc-dnsmasq.d:/etc/dnsmasq.d'
restart: unless-stopped
Now, Pi-hole has an option to add local DNS entries vai the GUI.
For Example, we could add portainer.tiny.pnet
and map it to 192.168.0.42:9000
. But then we have to add each device twice, once in Pi-hole and once in nginx.
To avoid the robotic work, I used a wildcard. While not possible via the GUI (see this reddit), all that is needed are two lines in a config. I created a new file in Pi-hole’s dnsmasq directory:
# /etc/dnsmasq.d/02-pnet.conf
address=/*.pnet/
address=/*.pnet/192.168.0.42
Restart Pi-hole after the config changes, and we are almost done.
What remains to do is to tell your devices to use Pi-hole as their DNS server. It does may or may not to be the primary one, the reverse proxy should work in either case. However, if a second DNS is configured, the ad-blocking won’t work. One could also set the Pi-hole as the primary (or secondary) DNS server in the fritzbox to expose it to all network devices.
To confirm that the DNS part is working, nslookup
should return something like this:
> nslookup portainer.tiny.pnet
Server: 100.100.100.100
Address: 100.100.100.100#53
Name: portainer.tiny.pnet
Address: 192.168.0.42
Split-DNS in Tailscale
In my case, using tailscale, the steps to get the reverse proxy also working when not at home were fairly simple.
- Login to the tailscale admin console and add a DNS entry
- Admin console > DNS > Add Nameserver > Custom
- Add Pi-hole ip
- Enable Split DNS
- Set domain to
pnet
Notes
- To reach my home-assistant container, I had to convince it to play nice with the reverse proxy. See here