How to Use Nginx as a Reverse Proxy: One Server, a Dozen Apps, Zero Port Confusion

Here’s a scenario you might recognize: you’re running Home Assistant on port 8123, Plex on 32400, a personal wiki on 8080, and Gitea on 3000. Every time you want to check something, you’re typing :8123 or :3000 like some kind of arcane incantation. There’s a better way.

Enter the reverse proxy — one of those concepts that sounds intimidating until you realize it’s really just a polite doorman for your network. In this tutorial, we’ll set up Nginx as a reverse proxy so all your self-hosted apps are accessible on standard ports with clean URLs like home.example.com and wiki.example.com.

What’s a Reverse Proxy, and Why Should I Care?

A reverse proxy sits between the internet and your applications. When a visitor hits wiki.example.com, the reverse proxy catches the request, figures out which internal service should handle it, forwards the request, gets the response, and hands it back to the visitor. The visitor never needs to know about ports or internal network topology.

Benefits:

  • No more memorizing port numbers
  • SSL termination in one place (HTTPS everywhere)
  • Add authentication or rate limiting centrally
  • Isolate your apps — if one gets compromised, the attacker still has to find the others
  • Clean URLs that don’t look like a developer’s mistake

Prerequisites

  • A Linux server (Ubuntu 22.04+ or similar) with a public IP
  • At least one self-hosted app already running on localhost (we’ll use port 8080 as an example)
  • A domain name you control (we’ll use example.com)
  • SSH access to your server
  • Basic comfort with terminal commands

Step 1: Install Nginx

If you don’t already have Nginx installed, it’s a one-liner:

sudo apt update && sudo apt install nginx -y

Verify it’s running:

sudo systemctl status nginx

You should see active (running). You can also visit your server’s IP in a browser — you’ll see the default Nginx welcome page.

Step 2: Point Your DNS

For each subdomain you want to use, add an A record pointing to your server’s IP. In your DNS provider’s dashboard:

  • app1.example.com203.0.113.50
  • app2.example.com203.0.113.50

Give DNS a few minutes to propagate (or use dig app1.example.com to check).

Step 3: Create a Reverse Proxy Config

Nginx stores virtual host configs in /etc/nginx/sites-available/. Create a new file for your first proxied app:

sudo nano /etc/nginx/sites-available/app1

Add the following configuration:

server {
    listen 80;
    server_name app1.example.com;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        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 $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

What’s happening here?

  • listen 80 — Accept HTTP traffic
  • server_name — Only respond to requests for this specific domain
  • proxy_pass — Forward matching requests to your app on localhost:8080
  • X-Real-IP and X-Forwarded-For — Pass the visitor’s real IP to your app (otherwise it would think all traffic comes from localhost)
  • Upgrade and Connection "upgrade" — Support WebSocket connections (needed by many modern apps like Home Assistant, code-server, etc.)

Step 4: Enable the Site

Nginx uses a symlink system — configs live in sites-available and get linked to sites-enabled:

sudo ln -s /etc/nginx/sites-available/app1 /etc/nginx/sites-enabled/
sudo nginx -t          # Test config syntax
sudo systemctl reload nginx

The nginx -t step is crucial — it catches syntax errors before you reload. If you see syntax is ok, you’re good to go.

Step 5: Add SSL with Let’s Encrypt

HTTP is fine for testing, but you want HTTPS. Certbot makes this trivial:

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d app1.example.com

Certbot will:

  1. Verify you control the domain
  2. Obtain a certificate from Let’s Encrypt
  3. Automatically modify your Nginx config to use HTTPS
  4. Set up auto-renewal (certificates expire every 90 days)

Test auto-renewal:

sudo certbot renew --dry-run

Step 6: Add More Apps

Repeat Steps 3-5 for each additional app. Create a new config file for each:

sudo nano /etc/nginx/sites-available/app2

Just change the server_name and proxy_pass port:

server {
    listen 80;
    server_name app2.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        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 $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Enable, test, reload, and get a cert for each one.

Common Pitfalls

502 Bad Gateway

ThisThis means Nginx can’t reach your app. Check that the app is running (curl http://127.0.0.1:8080) and listening on the right port. A common mistake: some apps bind to 0.0.0.0 (all interfaces) vs 127.0.0.1 (localhost only). For a reverse proxy setup, localhost-only is actually more secure.

Mixed Content Warnings

If your app loads CSS/JS over HTTP while served via HTTPS, browsers will block it. Make sure your app generates HTTPS URLs. Most apps have a config option or environment variable for this (often called BASE_URL or SITE_URL).

WebSocket Failures

If real-time features (live chat, terminal, notifications) stop working, you probably forgot the Upgrade and Connection headers. Go back and double-check Step 3.

Config Test Fails

Run sudo nginx -t and read the error carefully. The most common issues are missing semicolons and typos in proxy_pass URLs.

Bonus: A Template for Future Apps

Once you’ve done this a few times, you’ll want a copy-paste template. Here’s a complete one:

server {
    listen 80;
    server_name YOUR_SUBDOMAIN.example.com;

    location / {
        proxy_pass http://127.0.0.1:YOUR_PORT;
        proxy_http_version 1.1;
        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 $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Replace two values, enable the site, run certbot. Five minutes, tops.

Wrapping Up

A reverse proxy is one of those infrastructure upgrades that pays for itself almost immediately. Once you’ve set it up, adding new apps becomes a five-minute task instead of a port-number scavenger hunt. Plus, you get HTTPS everywhere for free, your apps see real visitor IPs, and your browser’s address bar stops looking like a phone number.

If you’re running a home lab with even two or three services, this is the single highest-leverage config change you can make. Set it up once, forget about it, and enjoy your clean, organized little corner of the internet.

Leave a Reply

Your email address will not be published. Required fields are marked *