How to install Nginx Proxy Manager with Docker and secure its admin panel
Run Nginx Proxy Manager with Docker Compose, keep the admin port off the internet, and add HTTPS proxy hosts with Let’s Encrypt, access lists and backups.
- Beginner
- 30 min read
- Updated
Tested on: Ubuntu 24.04 LTS, Ubuntu 26.04 LTS, Debian 12, Debian 13
This guide is not available in your language yet, so it is shown in English.
On this page
- Prerequisites
- Step 1 — Create the shared proxy network
- Step 2 — Create the Compose project
- Step 3 — Start NPM and create the admin account
- Step 4 — Allow web traffic in the firewall
- Step 5 — Connect an app to the proxy network
- Step 6 — Add a proxy host with HTTPS
- Step 7 — Restrict access with access lists
- Step 8 — Publish the admin panel safely (optional)
- Back up and restore
- Update Nginx Proxy Manager
- Troubleshooting
- 502 Bad Gateway
- The certificate request fails with Internal Error
- I cannot open the admin interface
- Login to the app stops working after adding an access list
- Address family not supported by protocol
- Bind for 0.0.0.0:80 failed: port is already allocated
- Next steps
Nginx Proxy Manager (NPM) puts a web interface on top of Nginx and Let's Encrypt. Instead of writing server blocks, you click together proxy hosts: a domain name, the app it forwards to, and a certificate that NPM requests and renews for you. It suits administrators who prefer a GUI and teams that share one server for many apps. This guide installs NPM with Docker Compose from its official image, keeps the admin port off the internet, creates the admin account through an SSH tunnel, connects apps over a shared Docker network, adds HTTPS and access lists, publishes the admin panel itself safely, and covers backups, updates and troubleshooting.
Prerequisites
- A server running Ubuntu 24.04 LTS, Ubuntu 26.04 LTS, Debian 12 or Debian 13 on amd64 or arm64. Since version 2.14, the image is no longer built for 32-bit ARM (armv7).
- A non-root user with
sudorights and SSH key login: Secure a new Linux server and Set up SSH keys. - Docker Engine with the Compose plugin: Install Docker on Ubuntu or Install Docker on Debian.
- A domain with A (and, with working IPv6, AAAA) records for each hostname, for example
app.example.com, pointing at the server. - Ports 80 and 443 free on the host.
The project does not publish minimum hardware requirements. The values below are a conservative starting point for NPM alone with its default SQLite database; add what your apps need.
| Resource | Minimum (official) | Suggested starting point |
|---|---|---|
| CPU | Not published | 1 vCPU, shared with your apps |
| RAM | Not published | 512 MB free for NPM |
| Disk | Not published | 2 GB free for the image, certificates and logs |
Step 1 — Create the shared proxy network
NPM reaches your apps over a Docker network that both sides join. Create it once:
docker network create proxyStep 2 — Create the Compose project
Create the project directory:
sudo mkdir -p /opt/npm
sudo chown $USER:$USER /opt/npm
cd /opt/npmCreate /opt/npm/compose.yaml. It follows the official example, with two changes: the admin port 81 is published only on 127.0.0.1, and the container joins the proxy network.
services:
app:
image: jc21/nginx-proxy-manager:2.16.0
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "127.0.0.1:81:81"
environment:
TZ: "Etc/UTC"
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
networks:
- proxy
networks:
proxy:
name: proxy
external: trueWhat the parts do:
- Image tag — the official setup page pins a version;
2.16.0is the current release at the time of writing. Check the project's GitHub releases page for the newest tag and use it. - Ports — 80 and 443 serve your sites and must be public. Port 81 is the admin interface; on
127.0.0.1only the server itself can open it. ./data— the SQLite database (database.sqlite), generated Nginx configs, custom certificates and logs../letsencrypt— Let's Encrypt certificates and account keys.TZ— set your time zone, for exampleEurope/Istanbul, so logs show local time.
NPM uses SQLite by default. It can also use MySQL/MariaDB or PostgreSQL through DB_MYSQL_* or DB_POSTGRES_* environment variables, which the setup page documents; SQLite is enough for a single server. If the server has no IPv6, add DISABLE_IPV6: "true" under environment.
Step 3 — Start NPM and create the admin account
Start the container and wait until it has finished its first start:
docker compose up -d
docker compose logs -f appOn the first run, NPM generates its keys and creates the database tables, which can take a minute or two. Stop following the logs with Ctrl+C once they settle down, and check the status:
docker compose psBecause port 81 listens only on 127.0.0.1, open it through an SSH tunnel from your own computer:
ssh -L 8181:127.0.0.1:81 your-user@203.0.113.10Keep that session open and browse to http://localhost:8181. Since version 2.13.0 there is no default login: NPM shows a setup screen where you create the first administrator. Use your real email address and a long, unique password, ideally from a password manager.
After logging in, turn on two-factor authentication for the admin account in your user settings; NPM supports TOTP apps since version 2.13.6.
Step 4 — Allow web traffic in the firewall
Allow SSH, HTTP and HTTPS in ufw (install it first on Debian with sudo apt install ufw):
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enableDo not rely on ufw for port 81. Docker's documentation states that published container ports bypass ufw, because Docker routes the traffic before ufw's rules apply. Binding the port to 127.0.0.1 in compose.yaml is what keeps it private. Check from another machine that it is closed:
nc -vz 203.0.113.10 81The connection must be refused or time out.
Step 5 — Connect an app to the proxy network
NPM forwards requests to apps by name over the proxy network. In the app's compose.yaml, remove the ports: entry and add the network, keeping default so the app still reaches its own database:
services:
app:
# keep image, environment and volumes from the app's guide
networks:
- default
- proxy
networks:
proxy:
name: proxy
external: trueRun docker compose up -d in the app's folder, then find the container's name:
docker network inspect proxy | grep '"Name"'Use the container name (for example n8n-n8n-1) as the forward hostname in NPM. Service names also resolve, but many projects call their service app or server, and on a shared network two services with the same name collide. The container name is unique on the server.
Step 6 — Add a proxy host with HTTPS
In the admin interface, open Hosts, then Proxy Hosts, and add a new proxy host:
- Domain names:
app.example.com. The DNS record must already point at the server. - Scheme:
http. The app speaks plain HTTP inside the Docker network; NPM handles HTTPS. - Forward hostname / IP: the container name from Step 5. Forward port: the port the app listens on inside its container, for example
5678. - Turn on WebSocket support if the app uses live updates, and Block common exploits.
- On the SSL tab, request a new Let's Encrypt certificate and turn on Force SSL, which redirects HTTP to HTTPS. HTTP/2 support can be on as well.
Labels can differ slightly between versions. Since version 2.13.0, NPM no longer asks for an email address or terms agreement when requesting a certificate. Save the host and test it:
curl -I http://app.example.com
curl -I https://app.example.comThe HTTP request returns a redirect to HTTPS, and the HTTPS request returns your app's response. NPM renews the certificates on its own.
Step 7 — Restrict access with access lists
An access list limits who can reach a proxy host. Open Access Lists and create one:
- on the authorization tab, add usernames and passwords for HTTP basic authentication,
- on the access tab, allow specific IP addresses or ranges (for example your office IP) and deny everything else,
- choose whether a visitor must satisfy any one of the rules or all of them.
Then open the proxy host, select the access list and save. Use access lists for tools without their own login. For apps that use HTTP basic authentication themselves, use only the IP rules: the project's FAQ explains that the access list and the app would both use the Authorization header, which breaks one of the two logins.
Step 8 — Publish the admin panel safely (optional)
The SSH tunnel is the safest way to manage NPM. If you want the admin panel at a normal HTTPS address instead, let NPM proxy to itself:
- Create an access list that allows only your own IP addresses and denies all others.
- Add a proxy host for
npm.example.comwith schemehttp, forward hostname127.0.0.1and forward port81. Inside the container,127.0.0.1is NPM itself, which is exactly the target here. - Request a certificate, turn on Force SSL, and select the access list.
Keep port 81 bound to 127.0.0.1 in compose.yaml so that the tunnel still works if you lock yourself out. With two-factor authentication and the IP access list, the admin panel is protected by three layers.
Back up and restore
Everything NPM knows lives in /opt/npm: compose.yaml, data/ (database, configs, logs) and letsencrypt/ (certificates and keys). Stop the container briefly so the SQLite database is copied in a consistent state. The folders belong to root, so use sudo:
sudo mkdir -p /opt/backups
cd /opt/npm
docker compose stop
sudo tar czf /opt/backups/npm-$(date +%F).tar.gz -C /opt npm
docker compose startTo restore on a new server with Docker installed, recreate the network, unpack the archive and start NPM:
docker network create proxy
sudo tar xzf /opt/backups/npm-2026-10-09.tar.gz -C /opt
cd /opt/npm
docker compose up -dReconnect your app projects to the proxy network as in Step 5. Keep the archive private, because it contains private keys, and copy it off the server. If you switched to MySQL/MariaDB or PostgreSQL, also dump that database with its own tool.
Update Nginx Proxy Manager
Check the GitHub releases page for the newest version and read its notes; some releases list extra upgrade steps. Take a backup, change the image tag in compose.yaml, for example from 2.16.0 to the new version, then pull and recreate:
cd /opt/npm
docker compose pull
docker compose up -d
docker compose logs --tail 50 appNPM updates its database and other requirements automatically on start. Open the admin interface afterwards and check that your proxy hosts and certificates are listed.
Troubleshooting
502 Bad Gateway
NPM cannot reach the app. Check that the forward hostname is the app's container name (not 127.0.0.1), that the app is attached to the proxy network (docker network inspect proxy), and that the forward port is the port inside the container, not a host port. Then look at docker compose logs app in the app's folder.
The certificate request fails with Internal Error
Let's Encrypt could not validate the domain. Check that the A and AAAA records point at this server (dig +short A app.example.com), that port 80 is reachable from the internet, and that no AAAA record points at an address the server does not answer on. Read docker compose logs app in /opt/npm for the detailed error. Repeated failures count against Let's Encrypt's limit of 5 failed validations per hostname per hour.
I cannot open the admin interface
Make sure the SSH tunnel is running and you browse to http://localhost:8181 on your own computer, not the server's address. Check that the container is up with docker compose ps, and give it a minute after the first start.
Login to the app stops working after adding an access list
The access list and the app both use the Authorization header. Remove the username and password rules from the access list and keep only IP rules, or rely on the app's own login.
Address family not supported by protocol
The server has no IPv6, but NPM tries to listen on it. Add DISABLE_IPV6: "true" under environment in compose.yaml and run docker compose up -d.
Bind for 0.0.0.0:80 failed: port is already allocated
Another web server, such as Nginx, Apache or Caddy, already uses port 80 or 443. Find it with sudo ss -tlpn 'sport = :80', stop and disable it, then run docker compose up -d again.
Next steps
- Learn the Compose features used here in Docker Compose basics.
- Prefer configuration as code? Compare with Caddy or Traefik.
- Strengthen SSH access, which also protects your admin tunnel, with Set up SSH keys.
- Find a server for your apps on the Docker hosting page.
- Read the official advanced configuration notes for custom Nginx snippets and environment options.
Frequently asked questions
What are the default login details for Nginx Proxy Manager?
Current versions have none. Since version 2.13.0, the first visit to the admin interface opens a setup screen where you create the administrator account. Older guides mention a default email and password, which no longer apply.
Can I just block port 81 with ufw?
No. Docker publishes container ports in a way that bypasses ufw, so a rule for port 81 has no effect. Publish the admin port on 127.0.0.1 in the Compose file instead and reach it through an SSH tunnel or a protected proxy host.
Why does forwarding to 127.0.0.1 give a 502 error?
Inside the Nginx Proxy Manager container, 127.0.0.1 is the container itself, not the server. Put your apps on the same Docker network and forward to their container name and container port.
Can I write my own Nginx directives?
Yes. Each proxy host has an advanced section for custom directives, and the project supports optional include files such as http.conf or server_proxy.conf under /data/nginx/custom for settings that apply to all hosts.
Does Nginx Proxy Manager support WebSockets?
Yes. Turn on WebSocket support in the proxy host settings for apps that need it, such as chat, dashboards or workflow editors.
Sources
- nginxproxymanager.com/guide
- nginxproxymanager.com/setup
- nginxproxymanager.com/advanced-config
- nginxproxymanager.com/upgrading
- nginxproxymanager.com/faq
- raw.githubusercontent.com/NginxProxyManager/nginx-proxy-manager/dev…
- raw.githubusercontent.com/NginxProxyManager/nginx-proxy-manager/dev…
- github.com/NginxProxyManager/nginx-proxy-manager/releases
- github.com/NginxProxyManager/nginx-proxy-manager/releases/tag/v2.13.0
- docs.docker.com/engine/network/packet-filtering-firewalls
- docs.docker.com/engine/network/port-publishing
- letsencrypt.org/docs/rate-limits