Nginx Proxy Manager (NPM) puts a web interface on top of Nginx. You add a domain, point it at a container or port, tick “Request SSL certificate”, and it handles the Nginx config and Let’s Encrypt renewals for you. It’s popular for self-hosting many apps on one VPS without editing config files.

When to use it

  • Good fit: a self-hosting server with several Docker apps, where you want point-and-click management.
  • Less ideal: complex configurations, infrastructure managed as code, or high-traffic sites where you want full control — then plain Nginx, Caddy or Traefik are better.

NPM needs ports 80 and 443, so it can’t run alongside another web server listening on them.

1. Install with Docker Compose

Requires Docker (install guide).

mkdir -p ~/npm && cd ~/npm

compose.yaml:

services:
  npm:
    image: jc21/nginx-proxy-manager:latest
    container_name: npm
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "127.0.0.1:81:81"     # admin UI — localhost only
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
    networks:
      - proxy

networks:
  proxy:
    name: proxy
docker compose up -d

The admin UI on port 81 is bound to localhost so it’s not exposed to the internet. Reach it through an SSH tunnel:

ssh -L 8181:127.0.0.1:81 user@your-server

Then open http://localhost:8181 on your computer.

2. First login

Recent versions ask you to create the admin account on first visit; older ones used the default [email protected] / changeme and forced a change. Either way, set a strong password and real email immediately.

3. Connect your apps to the proxy network

The cleanest setup: put app containers on the same Docker network as NPM, without publishing their ports at all. Example for an app stack:

services:
  whoami:
    image: traefik/whoami
    restart: unless-stopped
    networks:
      - proxy

networks:
  proxy:
    external: true

NPM can now reach it at http://whoami:80 by container name. The app has no public port, so it can’t be reached around the proxy.

Apps not in Docker (or on another network) are reachable via the host: use the server’s private IP or the Docker gateway (often 172.17.0.1) with the app bound to that address.

4. Add a proxy host

Hosts → Proxy Hosts → Add Proxy Host:

  • Domain Names: app.example.com (DNS must already point to the server).
  • Scheme / Forward Hostname / Port: http, whoami, 80.
  • Block Common Exploits: on.
  • Websockets Support: on for apps that need it (most modern dashboards do).

SSL tab:

  • Request a new SSL certificate with Let’s Encrypt.
  • Force SSL and HTTP/2 Support: on.
  • HSTS: on once you’re sure the domain will stay HTTPS.

Save. Within a few seconds the site is live with a valid certificate, renewed automatically.

5. Wildcard certificates (DNS challenge)

Under SSL Certificates → Add, choose “Use a DNS Challenge” and your DNS provider (Cloudflare and dozens of others are supported) with an API token. A *.example.com certificate can then be shared across all proxy hosts — and it works even for services not reachable on port 80.

6. Access lists

Access Lists add HTTP basic authentication and/or IP allow/deny rules. Attach one to admin tools that have no login of their own, or that should only be reachable from your office IP.

7. Custom Nginx config

Each proxy host has an Advanced tab where you can paste extra Nginx directives, e.g.:

client_max_body_size 2G;
proxy_read_timeout 300s;

Other host types

  • Redirection Hosts — redirect old.example.com to a new URL (301/302).
  • Streams — TCP/UDP forwarding (for example a game server or database port); you must also publish those ports on the NPM container.
  • 404 Hosts — answer a domain with a 404.

Backups and updates

All configuration and certificates are in ./data and ./letsencrypt:

tar czf ~/npm-backup-$(date +%F).tar.gz -C ~/npm data letsencrypt
docker compose pull && docker compose up -d

Security notes

  • Keep port 81 off the internet (bound to localhost as above).
  • Remember that Docker-published ports bypass UFW — only publish 80 and 443.
  • NPM is a single point of entry: keep it updated.

Troubleshooting

  • Certificate request fails — DNS doesn’t point to this server yet, or port 80 is blocked. Let’s Encrypt must reach http://your-domain/.well-known/acme-challenge/.
  • 502 Bad Gateway — NPM can’t reach the upstream: wrong container name, the app isn’t on the proxy network, or the wrong port (use the container’s internal port, not a published one).
  • Redirect loops behind Cloudflare — set Cloudflare SSL mode to “Full (strict)”, not “Flexible”.

For the hand-written equivalent, see Nginx reverse proxy for multiple Docker containers. A Docker VPS with 2–4 GB RAM comfortably runs NPM and a handful of self-hosted apps.

  • Nginx
  • Docker
  • Reverse Proxy