Why a local reverse proxy is useful
Running a handful of services on a home network—Nextcloud, a media server, a personal blog—usually means each one gets its own internal port. If you want to reach them from the outside, you could open every port on your router and forward it to the correct machine. That works, but it exposes those service ports to the internet and forces you to juggle a bunch of NAT rules. A reverse proxy on a single entry point keeps all those ports hidden and gives you a tidy URL scheme.
In this post I’ll walk through setting up Nginx as a local reverse proxy on a Debian‑based home server. The example covers three services:
| Service | Internal port | External URL |
|---|---|---|
| Nextcloud | 8443 | https://cloud.example.com |
| Plex | 32400 | https://media.example.com |
| Home Assistant | 8123 | https://home.example.com |
I’ll keep the discussion focused on practical commands, configuration snippets, and the trade‑offs you’ll run into.
Prerequisites
| Item | Why it matters |
|---|---|
| A machine with Debian 12 (Bookworm) or Ubuntu 24.04 LTS | Stable, long‑term support |
| Root or sudo access | Needed for package installation and systemd changes |
| A static local IP for the proxy server | Avoids IP churn that would break the proxy |
A public domain (e.g., example.com) |
Needed for HTTPS certificates |
| Port 80/443 open on your router | Nginx will listen on these ports |
If you’re on a different distribution, the package names and systemd paths will differ slightly, but the concepts stay the same.
1. Install Nginx
sudo apt update
sudo apt install nginx
Verify the service is running:
sudo systemctl status nginx
You should see active (running).
2. Prepare the domain and certificates
For a home server, Let’s Encrypt is the de‑facto standard. The certbot package can automatically obtain and renew certificates, but it requires that the domain points to your public IP. If you’re behind a dynamic DNS provider, set that up first.
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d cloud.example.com -d media.example.com -d home.example.com
certbot will edit Nginx config files for you and set up automatic renewal. If you prefer manual certs, place the fullchain.pem and privkey.pem files in /etc/ssl/private/ and reference them in the Nginx config.
3. Create a base server block
Create /etc/nginx/sites-available/proxy:
server {
listen 80;
listen 443 ssl http2;
server_name cloud.example.com media.example.com home.example.com;
ssl_certificate /etc/letsencrypt/live/cloud.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/cloud.example.com/privkey.pem;
include snippets/ssl-params.conf;
# Common security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
}
Link it to sites-enabled:
sudo ln -s /etc/nginx/sites-available/proxy /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
The ssl-params.conf snippet (from the Nginx docs) contains strong cipher suites and HSTS settings. You can copy it from the official Nginx repository or generate one with ssl-cert-snakeoil.
4. Add location blocks for each service
Append the following to the same file:
# Nextcloud
location / {
proxy_pass https://127.0.0.1:8443;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
# Plex
location /media/ {
proxy_pass https://127.0.0.1:32400;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_http_version 1.1;
proxy_set_header Connection "upgrade";
}
# Home Assistant
location /home/ {
proxy_pass https://127.0.0.1:8123;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
Notes on the configuration
proxy_passpoints to the internal service. Usinghttps://forces TLS even inside the LAN, which is a good habit if the service already runs with its own cert.- Headers:
X-Forwarded-Prototells the backend that the original request was HTTPS, which many apps need to build correct URLs. - Plex upgrade: Plex requires the
Connection: upgradeheader for WebSocket support. Without it, the web interface will break.
Reload Nginx:
sudo nginx -t
sudo systemctl reload nginx
5. Verify the setup
Open a browser and navigate to:
https://cloud.example.com→ Nextcloud login pagehttps://media.example.com/media/→ Plex web interfacehttps://home.example.com/home/→ Home Assistant dashboard
If any service fails, check its logs:
sudo journalctl -u nginx -f
Common pitfalls:
| Symptom | Likely cause |
|---|---|
| 502 Bad Gateway | Backend not listening on the expected port |
| SSL error in browser | Certificate mismatch or missing ssl_certificate |
| WebSocket errors in Plex | Missing Connection: upgrade header |
6. Keep the proxy secure
| Action | Why it matters |
|---|---|
| Disable HTTP | Force all traffic over TLS. Remove listen 80; if you prefer to use a separate HTTP‑to‑HTTPS redirect server. |
| Limit access to the proxy | Use a firewall rule to allow only your home network to reach ports 80/443. |
| Regularly update Nginx | sudo apt update && sudo apt upgrade nginx keeps you patched against CVEs. |
Use fail2ban |
Protect against brute‑force attempts on the admin interfaces. |
A minimal firewall rule set on Debian:
sudo ufw allow from 192.168.1.0/24 to any port 80,443 proto tcp
sudo ufw enable
7. Trade‑offs and alternatives
| Option | Pros | Cons |
|---|---|---|
| Nginx | Mature, low memory, excellent performance. | Slightly more complex config for websockets. |
| Caddy | Auto‑HTTPS, simpler config, built‑in ACME. | Smaller community, fewer extensions. |
| Traefik | Dynamic configuration via Docker/Kubernetes. | Overkill for a single‑box homelab. |
For most home users, Nginx remains the sweet spot because it’s already in the default repos and integrates well with certbot.
8. Troubleshooting checklist
-
Can you reach the proxy?
curl -I https://cloud.example.com→ Should return200 OK. -
Is the backend listening?
ss -tlnp | grep 8443(and other ports). -
Do the certificates match the domain?
openssl s_client -connect cloud.example.com:443 | openssl x509 -noout -subject. -
Are the firewall rules correct?
sudo ufw statusandsudo iptables -L -n. -
Check Nginx error logs
/var/log/nginx/error.log.
9. Automating renewals
certbot installs a cron job that runs twice a day. Verify it:
sudo systemctl list-timers | grep certbot
If you prefer systemd timers:
sudo systemctl enable certbot.timer
10. Wrap‑up
By funneling all external traffic through a single Nginx instance, you:
- Reduce the attack surface (only ports 80/443 exposed).
- Simplify DNS management (one A record per service).
- Gain a single point for SSL termination and logging.
The configuration above is a solid starting point. From here you can add authentication (e.g., auth_basic), rate limiting, or even a simple reverse‑proxy‑based VPN using stunnel or WireGuard.
Happy self‑hosting!
See also
- Turning a Raspberry Pi into a DIY Caddy Reverse Proxy for Home‑Hosted Services
- Why My Custom Systemd Timer Keeps Firing Twice and How to Fix It
- Using find + xargs to purge old log files without risking accidental deletes
- Why I Swapped Traefik for Nginx Ingress in My Home Reverse‑Proxy Stack.
- Why I Switched from Nginx to Caddy for My Home Reverse Proxy and What I Learned