← All articles
NETWORKING Nginx Proxy Manager: The Easiest Reverse Proxy for S... 2026-02-09 · nginx · reverse-proxy · ssl

Nginx Proxy Manager: The Easiest Reverse Proxy for Self-Hosting

Networking 2026-02-09 nginx reverse-proxy ssl networking

If you're running more than a couple of self-hosted services, you've probably hit the point where remembering port numbers gets old. Nextcloud is on :8080, Grafana on :3000, Gitea on :3001 -- and you're typing http://192.168.1.50:8080 into your browser like it's 2005. A reverse proxy fixes this by letting you access everything through clean subdomains like cloud.yourdomain.com and git.yourdomain.com, with HTTPS handled automatically.

The problem is that the most popular reverse proxy, Nginx, is configured through text files with a syntax that punishes small mistakes. Miss a semicolon, forget a closing brace, or get a proxy_pass directive slightly wrong, and you're staring at a white screen or a cryptic error. Nginx Proxy Manager (NPM) wraps all of that complexity in a web UI. You fill out a form, click save, and your service is live with a valid SSL certificate. No config files, no command line, no memorizing Nginx directives.

What Is a Reverse Proxy?

A reverse proxy sits between the internet and your services. Instead of exposing each application directly on its own port, all traffic comes through the proxy on ports 80 (HTTP) and 443 (HTTPS). The proxy looks at the hostname in the request -- cloud.yourdomain.com vs git.yourdomain.com -- and forwards it to the right backend service.

Internet :80 / :443 HTTPS Nginx Proxy Manager SSL termination Host-based routing Access control cloud.* git.* dash.* Nextcloud :8080 Gitea :3000 Grafana :3001 Docker internal network

This gives you:

Why Nginx Proxy Manager Exists

Nginx itself is battle-tested, extremely fast, and powers a huge chunk of the internet. But configuring it means writing server blocks like this:

server {
    listen 443 ssl http2;
    server_name cloud.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/cloud.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/cloud.yourdomain.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8080;
        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;
    }
}

Multiply that by ten services, add WebSocket support for some, configure certificate renewal with Certbot, and you're spending more time managing your proxy than running your actual services.

Nginx Proxy Manager was created by Jamie Curnow to solve exactly this problem. It's a Docker-based application that runs a full Nginx instance behind a clean admin UI. You get the performance and reliability of Nginx without ever touching a config file -- unless you want to.

Features

Setting Up Nginx Proxy Manager

Prerequisites

Docker Compose

Create a directory for NPM and add a docker-compose.yml:

services:
  npm:
    image: jc21/nginx-proxy-manager:latest
    container_name: nginx-proxy-manager
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "81:81"  # Admin UI
    volumes:
      - npm_data:/data
      - npm_letsencrypt:/etc/letsencrypt
    networks:
      - proxy

networks:
  proxy:
    name: proxy

volumes:
  npm_data:
  npm_letsencrypt:

Start it up:

docker compose up -d

That's it. Open http://your-server-ip:81 in your browser.

First Login

The default credentials are:

You'll be prompted to change these immediately. Do it -- this interface controls all your routing and SSL certificates.

Port 81 is the admin interface. Once you're comfortable with the setup, you should proxy the admin UI itself behind a subdomain (like npm.yourdomain.com) and restrict access to it. More on that below.

Adding Your First Proxy Host

Let's say you're running Grafana on port 3000 and want it available at grafana.yourdomain.com.

  1. Make sure grafana.yourdomain.com has a DNS A record pointing to your server's IP
  2. In the NPM admin UI, go to Hosts > Proxy Hosts and click Add Proxy Host
  3. Fill in the details:
    • Domain Names: grafana.yourdomain.com
    • Scheme: http
    • Forward Hostname / IP: 172.17.0.1 (or the container name if on the same Docker network)
    • Forward Port: 3000
  4. Click the SSL tab:
    • Select Request a new SSL Certificate
    • Check Force SSL (redirects HTTP to HTTPS)
    • Check HTTP/2 Support
    • Enter your email for Let's Encrypt
    • Agree to the terms of service
  5. Click Save

Within a few seconds, https://grafana.yourdomain.com is live with a valid certificate. NPM handles the Nginx config generation, the Let's Encrypt challenge, and the certificate installation -- all from that one form.

A note on networking

If your backend services run in Docker, the easiest approach is to put them on the same Docker network as NPM. In the compose example above, we created a network called proxy. Add your services to this network:

services:
  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    restart: unless-stopped
    networks:
      - proxy

networks:
  proxy:
    external: true

Then in NPM, use the container name (grafana) as the Forward Hostname instead of an IP address. This is more reliable than using IP addresses, which can change when containers restart.

SSL Certificate Renewal

Let's Encrypt certificates expire after 90 days. NPM automatically renews them before expiration -- you don't need to configure anything. The renewal process runs in the background and you'll only notice it if something goes wrong (usually a DNS issue or port 80 being blocked).

Access Lists

Not every service has built-in authentication. Some dashboards, admin panels, or internal tools are wide open by default. NPM's access lists let you add a layer of protection.

Basic Authentication

  1. Go to Access Lists and click Add Access List
  2. Name it (e.g., "Require Login")
  3. Under the Authorization tab, add username/password pairs
  4. Under the Access tab, optionally restrict by IP range
  5. Save the access list
  6. Edit your proxy host and select this access list from the dropdown

Now visitors must enter a username and password before reaching the backend service. This isn't a replacement for proper application-level authentication, but it's a useful first line of defense for services that lack it.

IP Restrictions

Access lists also support IP-based filtering. You can allow only your home IP, your VPN subnet, or a specific range. This is particularly useful for admin interfaces that should never be accessible from the public internet.

A common setup: create an access list that allows your local network (192.168.1.0/24) and your VPN range, then apply it to sensitive services. Everything else gets a basic auth prompt.

Redirection Hosts and Streams

Redirections

Need www.yourdomain.com to redirect to yourdomain.com? Or old.yourdomain.com to redirect to new.yourdomain.com? The Redirection Hosts section handles 301 and 302 redirects without any Nginx knowledge.

Streams

The Streams feature proxies raw TCP and UDP traffic -- anything that isn't HTTP. This is useful for:

Streams route based on port number rather than hostname, since non-HTTP protocols don't have a Host header.

Custom Nginx Configuration

For 90% of use cases, the UI handles everything. But sometimes you need a specific Nginx directive -- custom headers, WebSocket upgrades for a particular path, cache settings, or client body size limits.

Every proxy host has an Advanced tab where you can paste raw Nginx configuration. This gets injected into the server block for that host. For example, to increase the maximum upload size for a Nextcloud instance:

client_max_body_size 10G;
proxy_buffering off;
proxy_request_buffering off;

Or to add WebSocket support for a specific path:

location /ws {
    proxy_pass http://your-backend:8080;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

This is the escape hatch that makes NPM practical for advanced users. You get the convenience of the UI for the common stuff and raw Nginx power when you need it.

NPM vs. Other Reverse Proxies

Feature Nginx Proxy Manager Traefik Caddy Raw Nginx
Configuration Web UI Docker labels + YAML Caddyfile Config files
Learning curve Very low Moderate-steep Low-moderate Moderate-steep
Auto HTTPS Yes (Let's Encrypt) Yes (Let's Encrypt) Yes (built-in) Manual (Certbot)
Docker auto-discovery No Yes (built-in) No No
Adding a new service Fill out a form Add labels to container Edit Caddyfile Edit config, reload
Web dashboard Full management UI Read-only dashboard None Third-party
Access control (built-in) Basic auth, IP lists Basic auth, forward auth Basic auth Via config
TCP/UDP proxying Yes (Streams) Yes Yes (experimental) Yes
Resource usage ~150-200 MB RAM ~50-80 MB RAM ~30-50 MB RAM ~10-30 MB RAM
Custom configuration Advanced tab (per host) File provider, labels Caddyfile directives Full control
Best for Beginners, small setups Docker-heavy, many containers Simple config-file setups Maximum control

When to pick what

Nginx Proxy Manager is the right choice if you want the fastest path from "I have services on random ports" to "I have clean subdomains with HTTPS." The web UI means anyone in your household can understand what's going on, and you don't need to remember Nginx syntax. The downside is that every new service requires a manual trip to the UI.

Traefik is the better choice if you're running a Docker-heavy setup where containers come and go frequently. Traefik auto-discovers services through Docker labels, so adding a new service means adding a few labels to your compose file -- no separate proxy configuration to manage. The trade-off is a steeper learning curve and verbose label syntax. (We have a detailed Traefik guide if you want to explore that path.)

Caddy is a strong middle ground. Its Caddyfile syntax is human-readable and automatic HTTPS is built in with zero configuration. No web UI, but the config file is so simple it hardly matters. Good for people who are comfortable editing a text file but don't want Nginx's complexity.

Raw Nginx gives you maximum performance and maximum control. It's the right choice if you already know Nginx well, need every last bit of performance, or have requirements that no UI can accommodate. The cost is managing config files, certificate renewal, and reloads yourself.

Performance Considerations

NPM runs a full Nginx instance under the hood, so the actual proxying performance is identical to raw Nginx -- which is to say, excellent. Nginx handles thousands of concurrent connections with minimal resource usage.

The overhead of NPM itself comes from the management layer: a Node.js backend, a SQLite database for configuration, and the admin UI. This adds roughly 100-150 MB of RAM on top of what Nginx alone would use. For a homelab, this is negligible. If you're running on a Raspberry Pi with 1 GB of RAM, it's worth noting but still manageable.

The UI does not affect request processing. Once a proxy host is configured, requests flow through Nginx directly -- the Node.js management layer is not in the request path.

Common Issues

Port 80 or 443 already in use

If another web server (Apache, a different Nginx instance, or another application) is already listening on port 80 or 443, NPM's container won't start. Check with ss -tlnp | grep ':80\|:443' and stop the conflicting service. On many Linux distributions, Apache is installed and running by default.

DNS not set up correctly

NPM can't issue Let's Encrypt certificates if your domain doesn't resolve to your server's public IP. The Let's Encrypt HTTP challenge works by requesting a file from http://yourdomain.com/.well-known/acme-challenge/ -- if that request doesn't reach NPM, certificate issuance fails. Verify your DNS with dig yourdomain.com and make sure port 80 is reachable from the internet.

Certificate renewal failures

Renewals can fail silently if port 80 becomes blocked (firewall change, ISP issue) or DNS records change. Check the NPM logs periodically:

docker logs nginx-proxy-manager

If renewals are failing, the most common fixes are ensuring port 80 is open and that the DNS A record still points to the correct IP.

502 Bad Gateway

This means NPM received the request but couldn't reach the backend service. Common causes:

WebSocket connections dropping

Some applications (chat services, real-time dashboards, VS Code Server) use WebSockets. NPM handles WebSocket connections by default for most setups, but if you experience connection drops, add this to the Advanced tab of the proxy host:

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;

Securing the Admin Interface

The NPM admin UI on port 81 should not be exposed to the internet without protection. Best practices:

  1. Proxy it through itself -- create a proxy host for npm.yourdomain.com pointing to localhost:81, add SSL, and then close port 81 on your firewall so it's only accessible through the HTTPS subdomain.
  2. Apply an access list -- restrict access to your home IP or VPN subnet.
  3. Use a strong password -- the admin account controls your entire routing configuration.

Should You Use Nginx Proxy Manager?

Yes, if:

Probably not, if:

The honest take: Nginx Proxy Manager is the easiest way to go from exposed ports to proper subdomains with HTTPS. It democratizes reverse proxy management in a way that raw Nginx, Traefik, and Caddy don't -- you genuinely don't need to understand any networking concepts beyond "this service runs on this port." That simplicity comes at the cost of manual per-service configuration and slightly higher resource usage. For most people starting out with self-hosting, NPM is the right first reverse proxy. If you outgrow it, Traefik and Caddy are natural next steps.

Resources