Blog / Tutorials / How to Install Nginx Proxy Manager with Docker on a VPS
(last updated: )

How to Install Nginx Proxy Manager with Docker on a VPS

Nginx Proxy Manager (NPM) is a web interface for running an Nginx reverse proxy with free Let's Encrypt certificates. On a VPS you deploy it with a single Docker Compose file, point your domain names at the server and add each app as a proxy…

11 min read

Nginx Proxy Manager (NPM) is a web interface for running an Nginx reverse proxy with free Let’s Encrypt certificates. On a VPS you deploy it with a single Docker Compose file, point your domain names at the server and add each app as a proxy host. You never edit an Nginx config by hand, and one proxy can front many Docker apps.

What Is Nginx Proxy Manager and When Should You Use It?

NPM is a Docker-packaged front end for Nginx that turns proxy hosts, redirects, streams and TLS certificates into browser forms. It listens on ports 80 and 443 and forwards each request to the right container. For background, read our guide on what Nginx is and our comparison of a load balancer vs a reverse proxy.

It suits you if you run several Docker apps on one VPS and want HTTPS on each without writing server blocks. Look at Traefik or Caddy instead if you want proxy config in version-controlled files, or routes that appear automatically as containers start.

Nginx Proxy Manager Requirements and Ports

The project’s setup page assumes Docker and Docker Compose and uses three ports. It states no minimum CPU or RAM figures, so we give none.

RequirementDetailWhy it matters
Port 80Public HTTPHTTP traffic and Let’s Encrypt validation
Port 443Public HTTPSAll TLS traffic to your apps
Port 81Admin web UIKeep it off the public internet
DNSAn A record per hostnameNeeded before Let’s Encrypt can validate
SoftwareDocker and Docker ComposeNPM ships as a container image
CPU architectureamd64 or arm64armv7 images stopped at 2.14; use tag 2.13.7 there

Nginx Proxy Manager Docker Compose Setup

Install Docker first; our Docker install guide for Ubuntu covers it. Then save the file below as docker-compose.yml. It follows the project’s recommended layout (ports 80, 443 and 81, plus two bind-mounted volumes) with two changes: port 81 is bound to localhost, and the container joins a named Docker network we reuse later.

services:
  app:
    image: ‘jc21/nginx-proxy-manager:2.16.0’
    restart: unless-stopped
    ports:
      – ’80:80′                # public HTTP
      – ‘443:443’              # public HTTPS
      – ‘127.0.0.1:81:81’      # admin UI, reachable from the server itself only
    environment:
      TZ: ‘Europe/Berlin’
    volumes:
      – ./data:/data
      – ./letsencrypt:/etc/letsencrypt
    networks:
      – proxy

networks:
  proxy:
    name: proxy

By default NPM keeps its configuration in SQLite inside the ./data volume. If you prefer an external database, the documentation shows a MariaDB variant (linuxserver/mariadb) configured through DB_MYSQL_HOST, DB_MYSQL_PORT, DB_MYSQL_USER, DB_MYSQL_PASSWORD and DB_MYSQL_NAME; any DB_MYSQL_ variables take precedence over the SQLite ones.

mkdir -p ~/npm && cd ~/npm
# save the file above as docker-compose.yml, then:
docker compose up -d
docker compose ps
docker compose logs -f app

  1. Run docker compose up -d to pull the image and start the stack.
  2. Run docker compose ps and confirm the app service is Up.
  3. Watch docker compose logs -f app. The first run generates JWT keys, creates the database tables and a default admin user, which the docs say can take a couple of minutes.
  4. Open the admin UI on port 81 once the logs settle; the next section shows how to reach it safely.

Nginx Proxy Manager Default Login and First-Run Security

The current setup page says only that a default admin user will be created and lists no credentials. The advanced configuration page adds that setting INITIAL_ADMIN_EMAIL and INITIAL_ADMIN_PASSWORD creates the user at startup and skips the UI’s first-user setup screen, so a fresh install can show a screen where you create your own admin. Older tutorials tell you to sign in with [email protected] and changeme instead. Because this changed between releases, trust the screen your container shows.

  • From your workstation, open an SSH tunnel to the admin port: ssh -L 8081:127.0.0.1:81 user@your-server-ip.
  • Browse to http://localhost:8081 on your workstation.
  • If NPM asks you to create the first user, enter your own email address and a strong, unique password.
  • If you get a classic login form, an older release may be running. Older guides list [email protected] and changeme; sign in once, then change both immediately.
  • Optionally, set INITIAL_ADMIN_EMAIL and INITIAL_ADMIN_PASSWORD before the first start to skip the setup screen, and remove the password from the file afterwards.

Why the tunnel? The admin UI controls every certificate and route you own, so it should not sit on the open internet. Docker publishes ports through its own firewall rules, and per Docker’s documentation that traffic is handled before UFW’s rules apply, so a UFW rule blocking port 81 may not stop access to a published port. Binding to 127.0.0.1, as in the compose file, is the reliable fix. Allow only ports 22, 80 and 443 using our UFW guide, and protect SSH with Fail2Ban on a VPS.

Add Your First Proxy Host with a Free SSL Certificate

NPM requests certificates from Let’s Encrypt, the free certificate authority; our Let’s Encrypt guide covers how it works. Here is the flow for one app.

  1. Create an A record, for example app.example.com, pointing at your VPS’s IPv4 address, and wait for it to resolve.
  2. In NPM, go to Hosts, then Proxy Hosts, then Add Proxy Host.
  3. Enter the domain name, choose the scheme (http for most containers), and set the forward hostname and port of the app. For a container on the shared Docker network, use its service name.
  4. Turn on Websockets Support if the app uses WebSockets, and optionally Block Common Exploits.
  5. Open the SSL tab, choose Request a new SSL Certificate, enable Force SSL and HTTP/2 Support, enter your email address and accept the Let’s Encrypt terms.
  6. Save. NPM validates the domain over HTTP on port 80, issues the certificate and starts serving the app over HTTPS.

NPM Access Lists, Streams and Wildcard Certificates

  • Access lists: add basic HTTP authentication in front of a host that has no login of its own, such as a dashboard or log viewer. The project FAQ warns that basic auth uses the Authorization header, which most apps cannot share, so apps with their own login often break behind one.
  • Streams: forward raw TCP or UDP ports through NPM instead of HTTP. Example: forward a public port to a Git SSH service or game server in a container, and publish that port in the compose file too.
  • Wildcard certificates: Let’s Encrypt issues wildcards only through the DNS challenge. NPM supports it: you pick your DNS provider and supply its API credentials. Request *.example.com plus example.com once and reuse it across hosts.

Put Your Docker Apps Behind NPM

The cleanest reverse proxy Docker pattern is a shared network. The project documentation recommends a custom Docker network so upstream services need no published host ports; in NPM you enter the service name as the forward hostname, and names must be unique on that network. Our compose file already created a network called proxy, so apps on it stay off the internet except through NPM.

Take the stack from our n8n self-hosting guide. Keep its install as is and edit the compose file so the service joins the proxy network and drops its ports: line:

services:
  n8n:
    # image, volumes and environment stay as in your existing file
    # ports: removed – NPM reaches the container over the proxy network
    networks:
      – proxy

networks:
  proxy:
    external: true

Restart that stack, then add a proxy host with forward hostname n8n and forward port 5678 (n8n’s default port), with Websockets Support on. Never use localhost as the forward hostname for a container: inside the NPM container that address is NPM itself. This nginx proxy manager docker pattern, one shared network and no published app ports, scales to as many apps as your RAM allows.

Nginx Proxy Manager vs Traefik vs Caddy vs Pangolin

All four put HTTPS in front of your services but differ in how you configure them. Pangolin is the odd one out: a tunnelled reverse proxy built around WireGuard that solves a different access problem. Our Pangolin self-hosting guide covers that angle.

ToolConfig styleAutomatic HTTPSUIContainer discoveryBest for
NPMWeb UI forms; SQLite or MariaDBLet’s Encrypt from the UIYes, port 81No; enter host and portGentle start, a few apps
TraefikStatic config plus Docker labelsBuilt-in ACME supportDashboard for routesYes, via Docker labels; protect the socketMany changing containers, config as code (steeper curve)
CaddyCaddyfile or JSONOn by default; Let’s Encrypt and ZeroSSLNo web UINot built inShort config files (moderate curve)
PangolinDashboard; WireGuard tunnelsLet’s Encrypt, automatedYes, with users, roles and SSOTunnel-basedPrivate services behind NAT (a different model)

By use case: pick NPM for a UI and a handful of apps, Traefik for many containers with routing next to each service, Caddy for one short config file with HTTPS handled for you, and Pangolin for services behind a firewall or NAT.

Troubleshooting Nginx Proxy Manager 502 Errors and SSL Certificate Failures

  • Why do I see “502 Bad Gateway”? Nginx could not reach the upstream. Check the forward hostname, port and http/https scheme, that the container is running, and that it shares a Docker network with NPM (docker network inspect proxy). Using localhost as the hostname is a frequent cause.
  • Why does Let’s Encrypt say “DNS problem: NXDOMAIN looking up A for …”? The domain has no A record that resolves publicly. Fix the record and wait for DNS before retrying.
  • Why “Timeout during connect (likely firewall problem)” or “The server could not connect to the client to verify the domain”? Let’s Encrypt cannot reach port 80. Open it in every firewall layer; a local firewall status alone does not prove it is reachable from outside.
  • Why “too many certificates (5) already issued for this exact set of domains in the last 168 hours”? You hit the duplicate certificate limit of 5 per week for the same hostnames. Fix the cause, then wait.
  • Why does a WebSocket app load but then fail or hang? The proxy host needs Websockets Support switched on.
  • Why a redirect loop (ERR_TOO_MANY_REDIRECTS) behind Cloudflare? In Flexible mode Cloudflare talks to your origin over HTTP, and Force SSL redirects it back. Cloudflare’s guidance is Full or Full (strict) with an origin certificate, or removing the origin redirect.

Back Up and Update NPM

  • Back up ./data (the SQLite database and configuration) and ./letsencrypt (certificates). If you use MariaDB, dump that database too. Our server backup strategies guide shows how to ship copies off the server.
  • Update by changing the image tag in docker-compose.yml, then running docker compose pull followed by docker compose up -d. Read the project’s release notes first.
  • Pin the image tag instead of using latest, so updates happen when you decide, and note the previous tag for rollback.

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

Why Run Nginx Proxy Manager on a Contabo VPS

The proxy is light, so almost all of your server’s memory goes to the apps behind it. That is where Contabo’s RAM-per-Euro advantage matters: a Cloud VPS with a few gigabytes of RAM is enough for NPM plus several small Docker apps, and a larger plan gives the heavier apps room to grow without a jump in price. Plans are billed at a fixed monthly price, which keeps a self-hosted stack predictable.

Root access means you can install Docker and bind ports 80 and 443 exactly as shown above.

FAQ: Nginx Proxy Manager on Docker

What is the Nginx Proxy Manager default login?

The current documentation says only that a default admin user is created and lists no credentials. A fresh install can show a first-user setup screen where you choose your own email and password. Older tutorials mention [email protected] and changeme; change those at once if you see them.

Is Nginx Proxy Manager better than Traefik or Caddy?

It depends on how you like to work. NPM is easiest if you want a web UI and a few apps. Traefik suits many changing containers with labels, and Caddy suits short config files with automatic HTTPS. None is better in every case.

Why does NPM show a 502 Bad Gateway error?

A 502 Bad Gateway means Nginx could not reach the upstream app. The usual causes are a wrong forward hostname or port, the wrong http or https scheme, a stopped container, or the app not sharing a Docker network with the proxy. Never use localhost for a container.

How do I get a free SSL certificate in NPM?

Point an A record at your server, create a proxy host, open its SSL tab and request a new Let’s Encrypt certificate. Port 80 must be reachable for HTTP validation. For wildcard certificates, use the DNS challenge with your DNS provider’s API credentials.

Can I run NPM and other apps on the same VPS?

Yes, that is the intended use. Run NPM on ports 80 and 443, put your other Docker apps on the shared proxy network without published ports, and add one proxy host per app. Memory is usually the limit, so size the VPS for the apps, not the proxy.

Disclaimer: Product specifications, features, and prices mentioned in this article are subject to change and may vary by region, billing term, and active promotions. Please check each provider’s or brand’s official website for current figures, pricing, and local currency rates.