SSL
NPM
APP
Proxy Manager
domain • ssl • docker

Nginx Proxy Manager with Docker Compose and SSL

Run Nginx Proxy Manager with Docker Compose, point multiple domains to one server, reverse proxy internal apps, and request SSL certificates.

9 min read15/06/2026

When Nginx Proxy Manager makes sense

If you have several self-hosted apps at home or on a VPS, such as:

  • app:3000
  • gitea:3001
  • n8n:5678
  • home-assistant:8123

and want to use the same IP to route multiple domains or subdomains to different apps, Nginx Proxy Manager is one of the fastest ways to build a reverse proxy without hand-writing raw Nginx config.

then sooner or later you need a reverse proxy in front to:

  • point different domains or subdomains to different apps
  • request SSL for each hostname
  • avoid writing raw Nginx configs by hand

Nginx Proxy Manager, usually shortened to Nginx Proxy Manager (NPM), solves exactly that problem. It is a web UI on top of Nginx that lets you create proxy hosts, redirects, and SSL with a few basic forms.

Nginx Proxy Manager flow: domains hit NPM, NPM reverse proxies to different Docker apps and requests SSL automatically

Why use Nginx Proxy Manager instead of hand-writing Nginx config

NPM is not the only option, but it is a strong fit when you want:

  • fast setup
  • minimal config-file editing
  • a UI for many subdomains
  • Let's Encrypt SSL without building your own certbot flow

If you only have one or two apps and are very comfortable with plain Nginx, hand-written config is fine. But if you want to move quickly in a homelab or small VPS setup, NPM usually saves time.

Checklist before you click Request SSL

For Nginx Proxy Manager to issue SSL for a domain, you need all three:

  1. The domain or subdomain already points to the server IP
  2. Ports 80 and 443 can reach the machine running NPM
  3. The target app is already running on the local network or Docker network

Example:

  • app.example.com -> server IP
  • git.example.com -> server IP

If DNS is wrong or 80/443 are not reachable, the SSL request will fail.

Running Nginx Proxy Manager with Docker Compose

A minimal file:

services:
  npm:
    image: jc21/nginx-proxy-manager:latest
    restart: unless-stopped
    ports:
      - "80:80"
      - "81:81"
      - "443:443"
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt

Start it:

docker compose up -d

Port meanings:

  • 80: public HTTP for requests and ACME challenge
  • 443: public HTTPS after SSL is issued
  • 81: the NPM admin interface

Then open:

http://<server-ip>:81

The default credentials are usually:

Email:    [email protected]
Password: changeme

On first login, NPM asks you to change both.

Point the domain to the server first

If you want to publish app.example.com, create a DNS record like:

Type: A
Name: app
Value: <SERVER_PUBLIC_IP>

For multiple apps, add more hostnames:

  • git.example.com
  • n8n.example.com
  • dash.example.com

They can all point to the same IP because Nginx Proxy Manager will do the routing.

Create a Proxy Host in NPM

In the UI:

Hosts → Proxy Hosts → Add Proxy Host

Example values:

  • Domain Names: app.example.com
  • Scheme: http
  • Forward Hostname / IP: app
  • Forward Port: 3000

If the app runs in the same Docker network as NPM, use the Docker service name such as app. If the app runs outside Docker, use the internal IP such as 192.168.1.10.

You usually want:

  • Block Common Exploits
  • Websockets Support if the app needs websockets

Request SSL directly from the UI

After filling the proxy tab, open the SSL tab:

  • choose Request a new SSL Certificate
  • enable Force SSL
  • enable HTTP/2 Support
  • enable Agree to the Let's Encrypt Terms of Service
  • enter the email for certificate notifications

Click Save.

If DNS and ports 80/443 are correct, NPM will request the certificate and enable HTTPS for that hostname.

A practical example with multiple Docker services

Suppose you have:

docker-compose.yml for the app:

services:
  app:
    image: myapp:latest
    restart: unless-stopped
    expose:
      - "3000"

docker-compose.npm.yml for Nginx Proxy Manager:

services:
  npm:
    image: jc21/nginx-proxy-manager:latest
    restart: unless-stopped
    ports:
      - "80:80"
      - "81:81"
      - "443:443"
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt

If you want NPM to reach the app service by Docker name, both containers need to share a network. A simple pattern:

networks:
  proxy:
    external: true

Then attach both app and npm to the same proxy network.

At that point, in NPM you can simply enter:

  • Domain: app.example.com
  • Forward Hostname: app
  • Port: 3000

The most common mistakes

1. The domain is not pointing to the right IP

This is the most common reason SSL requests fail. Check the DNS A or CNAME record first.

2. Port 80 or 443 is already in use

If the machine already has Nginx, Apache, or another container binding 80/443, NPM may not start or the ACME challenge will fail.

Check:

ss -tulpn | grep -E ':80|:443|:81'

3. Wrong Forward Hostname

If NPM and the app run in Docker but on different networks, app will not resolve. In that case:

  • either put them on the same Docker network
  • or use the real internal IP of the app server

4. The app redirects HTTPS incorrectly

Some apps force HTTPS behind the proxy but do not correctly trust X-Forwarded-Proto, which creates redirect loops. If that happens, check the app's proxy-trust settings.

When NPM is not the best choice

NPM is very convenient, but not always the best option.

It is a weaker fit if:

  • you want very deep custom Nginx configuration
  • you prefer strict infrastructure-as-code instead of a UI
  • you want dynamic reverse proxy discovery like Traefik labels

But if the goal is simply "make the app reachable with a domain and SSL", Nginx Proxy Manager remains a very practical tool.

Conclusion

Nginx Proxy Manager is a clean way to build reverse proxying for a homelab or small VPS:

  • quick to run with Docker Compose
  • easy to point many domains to different apps
  • easy to request Let's Encrypt SSL directly from the UI

If you already have a few internal containers and do not want to hand-write Nginx configs yet, this is usually one of the best starting points.