How to set up a local Nginx reverse proxy for home‑hosted services without exposing ports to the internet

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_pass points to the internal service. Using https:// forces TLS even inside the LAN, which is a good habit if the service already runs with its own cert.
  • Headers: X-Forwarded-Proto tells the backend that the original request was HTTPS, which many apps need to build correct URLs.
  • Plex upgrade: Plex requires the Connection: upgrade header 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 page
  • https://media.example.com/media/ → Plex web interface
  • https://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

  1. Can you reach the proxy?
    curl -I https://cloud.example.com → Should return 200 OK.

  2. Is the backend listening?
    ss -tlnp | grep 8443 (and other ports).

  3. Do the certificates match the domain?
    openssl s_client -connect cloud.example.com:443 | openssl x509 -noout -subject.

  4. Are the firewall rules correct?
    sudo ufw status and sudo iptables -L -n.

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