How to Set Up Nginx Proxy Manager: The Easiest Reverse Proxy for Self-Hosters

How to Set Up Nginx Proxy Manager: The Easiest Reverse Proxy for Self-Hosters

Set up Nginx Proxy Manager in Docker and manage SSL certificates, domains, and reverse proxies without touching a config file.

If you’re running multiple services on your homelab, you’ve hit the same problem I have: how do you route app1.example.com to one container, app2.example.com to another, and handle SSL for all of them without manually editing nginx config files every time?

Nginx Proxy Manager (NPM) is the answer. It’s a reverse proxy with a web UI that handles all the tedious stuff — SSL certificates, domain routing, access control — without requiring you to touch a single configuration file.

I switched from manually managing nginx configs to NPM about a year ago, and honestly, I’ve never looked back. If you find nginx configs intimidating (or just tedious), NPM is a game-changer.

What’s a Reverse Proxy Anyway?

Quick primer: A reverse proxy sits in front of your services and directs incoming traffic to the right place. Here’s why you need one:

SSL/HTTPS for everything: Your Vaultwarden instance, Jellyfin, Nextcloud — they all need HTTPS. A reverse proxy terminates SSL at one place, so you don’t configure it on every app.

Multiple domains, one server: Instead of running apps on random ports like localhost:8080, localhost:9000, you can use clean URLs: vault.example.com, media.example.com.

Access control: Block traffic, set IP whitelists, or require authentication before reaching your apps.

Performance: Caching, compression, load balancing across containers.

Easy updates: Update an app without losing your SSL config or routing rules.

NPM vs. Traefik vs. Caddy

I get asked this constantly. Here’s my honest take:

Traefik: Incredibly powerful, but configuration lives in YAML or Docker labels. If you like that, great. I find it overkill for a homelab with 5-10 services.

Caddy: Fantastic, minimal Caddyfile syntax, auto-SSL. We use Caddy for Vaultwarden because it’s lightweight. Downside: no web UI for non-technical users.

Nginx Proxy Manager: Dead simple web UI, point-and-click setup, auto-SSL via Let’s Encrypt. Perfect for homelab runners who want to avoid config files entirely. The trade-off? Slightly less flexible than pure nginx, but you gain 100 times better UX.

For a self-hosted setup with 3+ services? I pick NPM every time.

What You’ll Need

  • Docker & Docker Compose: You know the drill. Guide here if needed.
  • A domain name: With DNS pointing to your server. NPM will handle the Let’s Encrypt magic.
  • A server with port 80/443 accessible: Needed for SSL certificate validation.

That’s it. Seriously. No config files, no nginx knowledge required.

Step 1: Install Nginx Proxy Manager

Create a directory:

mkdir -p ~/docker/npm
cd ~/docker/npm

Create docker-compose.yml:

version: '3.8'

services:
  app:
    image: 'jc21/nginx-proxy-manager:latest'
    restart: unless-stopped
    container_name: npm
    ports:
      - '80:80'
      - '443:443'
      - '81:81'
    environment:
      DB_MYSQL_HOST: 'db'
      DB_MYSQL_PORT: 3306
      DB_MYSQL_USER: 'npm'
      DB_MYSQL_PASSWORD: 'npm-password'
      DB_MYSQL_NAME: 'npm'
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
    depends_on:
      - db

  db:
    image: 'jc21/mariadb-aria:latest'
    restart: unless-stopped
    container_name: npm-db
    environment:
      MYSQL_ROOT_PASSWORD: 'root-password'
      MYSQL_DATABASE: 'npm'
      MYSQL_USER: 'npm'
      MYSQL_PASSWORD: 'npm-password'
    volumes:
      - ./data/mysql:/var/lib/mysql

Security note: Change those placeholder passwords (npm-password, root-password). Use something strong.

Start it up:

docker compose up -d

Verify it’s running:

docker compose ps

You should see both npm and npm-db containers running.

Step 2: First Login

Open your browser to http://your-server:81

Default credentials:

Change these immediately — go to Settings and update your admin email and password.

Step 3: Point Your Domain

Make sure your domain’s A record points to your server:

dig yourdomain.com +short
# Should return your server's IP

NPM will use this to validate Let’s Encrypt certificates. Don’t skip this step or SSL won’t work.

Step 4: Add Your First Proxy Host

This is where NPM shines. Let’s route app.yourdomain.com to a service running on port 3000 locally.

  1. In the NPM admin panel, go to Proxy Hosts
  2. Click Add Proxy Host
  3. Fill in:
    • Domain Names: app.yourdomain.com
    • Scheme: http (or https if your app already uses SSL)
    • Forward Hostname/IP: localhost or your service container name
    • Forward Port: 3000 (whatever your app is running on)
  4. Go to the SSL tab
  5. Select Request a new SSL Certificate
  6. Check Force SSL to always redirect HTTP → HTTPS
  7. Click Save

NPM will request a Let’s Encrypt certificate automatically. This usually takes 30 seconds.

That’s it. You now have https://app.yourdomain.com pointing to your service with a valid SSL certificate. No manual nginx editing. No certificate renewal scripts. It just works.

Step 5: Add More Services

Repeat Step 4 for every service:

  • vault.yourdomain.com → your Vaultwarden instance on port 8080
  • media.yourdomain.com → Jellyfin on port 8096
  • cloud.yourdomain.com → Nextcloud on port 80

Each one gets its own proxy host, its own Let’s Encrypt certificate, and its own routing rule. All from the web UI.

Access Control: Block Traffic You Don’t Want

NPM includes Access Lists for IP whitelisting and authentication.

IP Whitelisting

If you only want your home IP accessing something:

  1. Go to Access ListsAdd Access List
  2. Name it: Home IP
  3. Add your IP address
  4. Go to your proxy host → Access tab
  5. Select your access list

Now only your IP can reach that service. Attempts from elsewhere get a 403.

HTTP Basic Auth

For on-the-go access without hardcoding IPs:

  1. Access ListsAdd Access List
  2. Enable HTTP Basic Auth
  3. Add username/password
  4. Assign to proxy host

When accessing, you’ll be prompted for credentials. Simple but effective.

Custom Locations & Advanced Routing

Sometimes you need fancier routing. NPM supports Custom Locations:

  1. Go to your proxy host → Locations tab
  2. Click Add Location
  3. Set path: /api/special
  4. Forward to a different hostname/port
  5. Add custom headers or rules

Example: Route /api/* requests to one container, everything else to another.

Common Gotchas

SSL certificate won’t issue

The most common culprit: your domain’s DNS doesn’t actually point to your server. Verify:

dig yourdomain.com

If you see your IP, DNS is fine. If not, wait 5-10 minutes and try again.

“502 Bad Gateway” errors

This means NPM can’t reach your service. Check:

# From inside NPM container
docker exec npm curl http://your-service-container:port

# Or if using localhost
docker exec npm curl http://localhost:8080

If the container is running but unreachable, make sure it’s on the same Docker network or accessible from the host.

HTTPS to your app, but NPM is HTTP

If your app runs on port 443 internally, set the Forward Scheme to https. NPM will communicate with it securely.

Large file uploads failing

By default, nginx has a 1MB body size limit. Increase it per proxy host:

  1. Go to proxy host → Advanced tab
  2. Paste in the Custom Nginx Configuration field:
client_max_body_size 512M;

This allows 512MB uploads. Adjust as needed.

Maintenance & Updates

NPM updates are painless:

cd ~/docker/npm
docker compose pull
docker compose down
docker compose up -d

SSL certificates renew automatically 30 days before expiry. You’ll never see a warning.

Backups are important:

# Backup the data directory
tar -czf npm-backup-$(date +%Y%m%d).tar.gz ./data ./letsencrypt

Consider a monthly cron job for this.

Performance Tips

Enable gzip compression: Go to proxy host → Advanced → add:

gzip on;
gzip_types text/plain text/css application/json application/javascript;

Set reasonable timeouts: For slow apps, increase read timeouts:

proxy_read_timeout 300s;

Use a CDN: If you’re exposing NPM to the internet, consider Cloudflare or similar for DDoS protection.

Monitoring Your Setup

Check that Let’s Encrypt certificates are renewing:

docker logs npm | grep -i certificate

Monitor disk usage for logs:

du -sh ~/docker/npm/

NPM logs accumulate. Clean old logs occasionally:

docker exec npm-db find /data/logs -mtime +30 -delete

FAQ

Can I use NPM with a self-signed certificate?

Yes, but your clients will see SSL warnings. For local-only access that’s fine. For external access, use Let’s Encrypt (it’s free).

Does NPM work on ARM (Raspberry Pi)?

Yes. The jc21/nginx-proxy-manager image supports ARM64. RPi4 handles it easily.

Can I run NPM behind another reverse proxy?

Technically yes, but it gets complex. Keep NPM at the edge (ports 80/443) when possible.

How much memory does NPM use?

Surprisingly little. The container typically uses 100-150MB. The MariaDB database might use 200-300MB depending on how many proxy hosts you have. Even on a 1GB VPS, it’s not a bottleneck.

What if my ISP blocks port 80/443?

You’re out of luck for public HTTPS. Consider a VPS with an IP address that can reach those ports, and run NPM there instead.

Can I export my proxy hosts configuration?

The database lives in ./data/mysql/. Back it up regularly. There’s no built-in export, but the database can be restored to any NPM instance.

Is NPM suitable for production?

Absolutely. We’re running it for multiple personal sites and services. Stability is excellent. That said, if you’re running a large-scale multi-tenant operation, you might outgrow it into a dedicated reverse proxy cluster.

Next Steps

Now that you have NPM managing your domains and SSL:

  1. Add all your self-hosted services behind it
  2. Set up monitoring/alerting for certificate renewal (though it’s automatic)
  3. Explore access control for sensitive services
  4. Consider adding a Wireguard VPN so you don’t expose everything to the internet

You’ve just eliminated a huge pain point in self-hosting. No more fiddling with nginx configs, no more manual certificate renewal. That’s a win.


Resources:

Stay in the loop 📬

Get self-hosting tutorials, tool reviews, and infrastructure tips delivered to your inbox. No spam, unsubscribe anytime.

Join 0 self-hosters. Free forever.