What is Nginx Proxy Manager?
Nginx Proxy Manager is a web interface for running Nginx as a reverse proxy, with free SSL certificates from Let's Encrypt. It is open source under the MIT license and lets you point domains at your self-hosted apps, add HTTPS, set up redirects and protect services with access lists, all without writing Nginx configuration by hand.
Requirements
- A Linux server with 1 vCPU and 512 MB of RAM.
- Docker Engine and Docker Compose v2.
- Ports 80 and 443 not used by anything else on the host, and open in your firewall (and forwarded on your router for home servers).
- One or more domain names with A or AAAA records pointing at the server.
Step 1: Prepare the server
This guide assumes Ubuntu 24.04 with Docker installed. If you need Docker, follow the official install guide. Check that nothing else is listening on ports 80 and 443:
sudo ss -ltnp | grep -E ':(80|443)\s'
Then create a folder and a shared Docker network that your apps can join:
mkdir -p ~/npm && cd ~/npm
docker network create proxy
Step 2: Create the Docker Compose file
Save this as docker-compose.yml:
services:
app:
image: jc21/nginx-proxy-manager:2
container_name: nginx-proxy-manager
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "127.0.0.1:81:81"
environment:
TZ: "Europe/London"
# Uncomment if IPv6 is not enabled on your host:
# DISABLE_IPV6: "true"
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
networks:
- proxy
networks:
proxy:
external: true
Nginx Proxy Manager uses a built-in SQLite database by default, which is fine for almost every setup. It needs no secrets in the Compose file; you create the admin account in the browser.
The admin port 81 is bound to 127.0.0.1. The admin UI controls every domain and certificate on the server, so it should not be open to the internet. Reach it through an SSH tunnel (Step 3), or proxy it through NPM itself with an access list. The 2 tag tracks the current 2.x release; pin a specific version such as 2.16.0 from the releases page if you prefer.
Step 3: Start and open the app
docker compose up -d
docker compose logs -f app
The first start generates keys and initialises the database, which can take a minute. From your own computer, open an SSH tunnel:
ssh -L 8181:127.0.0.1:81 user@YOUR_SERVER_IP
Then browse to http://localhost:8181. Current versions show a setup screen where you create the first admin user with your email and a strong password. Enable two-factor authentication in your profile if your version offers it.
Step 4: Put it behind HTTPS
Nginx Proxy Manager is itself the HTTPS layer, so this step is about adding your first site. Click "Add Proxy Host" and fill in:
- Domain Names:
app.example.com - Forward Hostname / IP: the container name of your app on the
proxynetwork, such asuptime-kuma - Forward Port: the app's internal port, such as
3001 - Websockets Support: on, for apps with live updates
On the SSL tab, choose "Request a new SSL Certificate", enable "Force SSL" and "HTTP/2 Support", and save. The equivalent in Caddy, for comparison, is just:
app.example.com {
reverse_proxy uptime-kuma:3001
}
To make an app reachable by name, add networks: [proxy] to its service and declare proxy as an external network in its Compose file. You can then remove the app's own ports: mapping so it is reachable only through the proxy.
Backups and upgrades
Back up both folders: ./data (database, proxy host configuration, access lists and logs) and ./letsencrypt (certificates and account keys). Stop the container for a consistent copy:
docker compose stop && tar czf npm-$(date +%F).tgz data letsencrypt && docker compose start
Upgrade with:
docker compose pull && docker compose up -d
Troubleshooting
- Certificate request fails: the DNS record does not point at this server yet, or port 80 is blocked. Let's Encrypt HTTP validation needs port 80 open from the internet. For servers behind CGNAT, use a DNS challenge instead.
- 502 Bad Gateway: NPM cannot reach the upstream. Use the container name on a shared network, not
localhost, which refers to the NPM container itself. - Container fails with "address already in use": another web server, often Apache or Nginx from the OS, is using port 80 or 443. Stop and disable it.
- Uploads fail with 413: add
client_max_body_size 1G;under the proxy host's Advanced tab.
Next steps
Add access lists for admin tools, create redirection hosts for old domains, set up a wildcard certificate with a DNS challenge, and put every self-hosted app behind NPM on the shared proxy network.
Spotted something out of date? Tell us and we will update the guide.