Reverse Proxy Configuration Generator
Put Nginx in front of the apps you self-host on a Raspberry Pi — Home Assistant, dashboards,
Docker containers, anything listening on a local port — without hand-writing the config. Pick a
starting preset (plain HTTP on your LAN, HTTPS with Let's Encrypt, a WebSocket app, or a Docker
container), add one card per app/subdomain, and get a live nginx site configuration you can
copy straight to /etc/nginx/sites-available/. The generator checks the mistakes that actually
break reverse proxies: TLS with no certificate paths, a proxy pointed at its own port, duplicate
server_names, missing WebSocket upgrade headers — and hands you the exact install
commands, including nginx -t, which validates the file before it is ever loaded.
1 · Starting point
Pick a preset to fill the form with a working baseline. Choosing a preset replaces the current apps — everything can be edited afterwards.
2 · Apps (one card per subdomain)
Each app gets its own server block. Point each one at the port the app actually listens on locally (find it with ss -tlnp | grep nginx… sorry — ss -tlnp).
3 · Global settings
Import an existing config (optional)
Paste an nginx site file (e.g. from cat /etc/nginx/sites-available/mysite) or upload it — it is parsed in your browser and loaded into the form above. One card per server block that has a proxy_pass; TLS settings, certificate paths and upload size are picked up too. Everything else is reported and left out.
Your reverse proxy configuration
Install it (three commands)
Run on the Raspberry Pi. Step 3 is the safety net: nginx -t validates the file and only reloads if it parses — a typo can't take Nginx (and every proxied app) down.
How a reverse proxy works on a Pi
Your apps listen on local ports (127.0.0.1:8080, :8123…) so strangers can't reach them directly. Nginx is the single public front door: it listens on 80/443, matches the request's Host header to a server_name, and forwards the request to the right local port. One Pi, many subdomains — homeassistant.pi.example.com and grafana.pi.example.com can both point at the same machine's IP and land on different apps.
The proxy also terminates TLS (the certificate lives in Nginx, not in every app), can add authentication in front of apps that have none, and tells the real client address to the app through the X-Forwarded-For header — which is why well-behaved apps show your IP instead of 127.0.0.1 in their logs.
The mistakes that actually break reverse proxies
- WebSockets silently fail (or time out after 60 seconds). Nginx's default
Connection: closehandling kills WebSocket handshakes. The fix is theUpgrade/Connection "upgrade"header pair plus a longproxy_read_timeout— tick WebSocket on any app with live updates and this generator writes both. - The trailing-slash trap in
proxy_pass.location /app/ { proxy_pass http://127.0.0.1:8080; }forwards the full path (/app/login), butproxy_pass http://127.0.0.1:8080/;(with a slash) strips the/appprefix first. Most apps expect their own root, so this generator adds the trailing slash when you set a path prefix — and flags it if your app is one of the few that wants the full path. - The proxy points at its own port.
proxy_pass http://127.0.0.1:80where Nginx itself listens on 80 is an infinite loop — requests bounce forever until they time out. The generator flags any backend port that equals the listen port. - certbot can't renew because port 80 is blocked or taken. If your app owns port 80, either move it to another port and let Nginx serve 80 (the redirect server here doubles as the ACME webroot), or use
certbot --nginx. A certificate that can't renew is a site that breaks in ~90 days. - The app generates
http://links behind HTTPS. Apps that build absolute URLs from the request seeX-Forwarded-Proto: http(or nothing) and hand out insecure links. This generator always sendsX-Forwarded-Proto; if an app still misbehaves, it usually needs a one-line setting likeTRUSTED_PROXYorPUBLIC_URL. - Duplicate
server_nameacross site files. Twoserverblocks claiming the same name (often in two different files undersites-enabled/) and Nginx serves whichever it reads first — your edits to the "right" file appear to do nothing. The generator flags duplicates within this config; check old files withgrep -r server_name /etc/nginx/sites-enabled/.
Key directives reference
| Directive | What it does |
|---|---|
listen 443 ssl; | Answer on port 443 and terminate TLS. (On modern Nginx you can add http2, but HTTP/2 over a Pi is often more trouble than it's worth.) |
server_name | The Host header this block answers for. One per subdomain; the first block in a file is the default for anything unmatched. |
proxy_pass http://host:port; | Where the request actually goes. Trailing slash = strip the location prefix before forwarding. |
proxy_set_header Host $host; | Forward the original hostname (not 127.0.0.1) so virtual-hosted apps and URL generation work. |
proxy_set_header X-Real-IP / X-Forwarded-For | Tell the app who really asked — without these, every request looks like it came from localhost. |
proxy_set_header X-Forwarded-Proto $scheme; | Tells the app the original scheme (http/https) so it builds https:// links behind TLS. |
Upgrade / Connection "upgrade" | The WebSocket handshake pair. Only needed on locations that carry WebSockets — harmless elsewhere, but this generator keeps them out of plain-HTTP locations. |
client_max_body_size | Largest request body Nginx accepts before answering 413. The default is 1M — uploads bigger than that fail at the proxy, not the app. |
proxy_read_timeout | How long to wait between chunks from the app before Nginx answers 504. WebSockets and streaming need it raised well past the 60s default. |
auth_basic + auth_basic_user_file | Password gate in front of an app that has no login of its own. Create the file with htpasswd -c /etc/nginx/.htpasswd <user>. |
Related tools
The rest of the home-lab wiring that goes with a reverse proxy:
Suggested
Luke Keam | techgeek.biz