How to Install Nginx Proxy Manager on Ubuntu 24.04 VPS — GUI Reverse Proxy with Auto SSL
Managing multiple web services on a single VPS almost always ends up looking the same: a mess of hand-edited Nginx config files, manual Certbot runs, and 2 AM debugging sessions when a certificate renewal silently fails. Nginx Proxy Manager (NPM) replaces all of that with a clean web UI. You point a domain at your server, click a few buttons, and NPM handles the reverse proxy config, the Let's Encrypt certificate, and the auto-renewal for you.
This guide walks you through installing Nginx Proxy Manager on an Ubuntu 24.04 VPS using Docker and Docker Compose, from the first SSH connection to a hardened production deployment serving multiple proxied apps with SSL.
Skip the setup? Deploy Nginx Proxy Manager in one click with our pre-configured app image. Launch a CloudCore Starter VPS now and have your reverse proxy online in under 60 seconds.
Table of Contents
What is Nginx Proxy Manager?
Nginx Proxy Manager is an open-source web application that gives you a friendly GUI on top of Nginx. Under the hood, it runs an actual Nginx instance — the same battle-tested server that powers a large portion of the public internet — but you never have to touch a .conf file unless you want to. Every proxy host, redirect, SSL certificate, and access rule is configured through a browser dashboard.
The core feature set covers almost everything most users need from a reverse proxy. You can create proxy hosts that forward requests from a public domain to any upstream service running on your LAN, on the host, or in another container. You can request and manage Let's Encrypt certificates with a single click, including wildcard certificates via DNS-01 challenges. You can define access lists that restrict who can reach a given host, either by IP allowlist or HTTP basic authentication. You can set up redirection hosts that 301 old domains to new ones, 404 hosts that serve a clean landing page for unmapped subdomains, and streams that pass raw TCP or UDP traffic through to backend services like game servers, databases, or SSH bastions.
Typical use cases include fronting a Home Assistant server with SSL, exposing a self-hosted Vaultwarden or Nextcloud instance to the internet, publishing multiple apps (Plex, Jellyfin, Portainer, Grafana) on subdomains of a single VPS, or sitting in front of a Docker-based homelab to provide one unified entrypoint on ports 80 and 443. Any time you would reach for Traefik or raw Nginx but prefer a web UI, NPM is the right tool.
Why Use NPM Instead of Raw Nginx?
Raw Nginx is powerful and fast, but the ergonomics are rough if you are not a full-time sysadmin. NPM solves a handful of very specific pains:
- No config file editing — Every proxy host, SSL cert, and access rule is a form in the UI. NPM writes the Nginx config for you and reloads the process atomically.
- Automatic Let's Encrypt — Request a cert with two clicks. NPM handles the HTTP-01 or DNS-01 challenge, installs the cert, and renews it on a schedule. No
certbotcron job to babysit. - Wildcard certificates out of the box — Pick your DNS provider from a dropdown (Cloudflare, Route 53, DigitalOcean, DuckDNS, and dozens more) and NPM completes the DNS-01 challenge for
*.yourdomain.comautomatically. - Access lists built in — IP allowlists and basic auth are checkboxes, not custom Nginx blocks. Great for admin dashboards you do not want exposed publicly.
- Multi-user with audit log — Invite team members, each with their own login. Every change is logged, so you know who edited which host and when.
- Streams support — Proxy non-HTTP traffic (TCP/UDP) for game servers, databases, or custom protocols — something most Nginx-on-a-GUI tools do not support.
- Custom Nginx snippets — You are never locked in. Each proxy host has an "Advanced" tab where you can paste arbitrary Nginx directives if you need something NPM's UI does not expose.
Nginx Proxy Manager vs. Alternatives
| Tool | UI | Auto SSL | Streams (TCP/UDP) | Docker-native | Best For |
|---|---|---|---|---|---|
| Nginx Proxy Manager | Full web UI | Yes (HTTP-01 + DNS-01) | Yes | Yes | Self-hosters, homelabs, small SaaS |
| Traefik | Dashboard (read-only) | Yes | Yes (v3+) | Yes | Dynamic Docker/Kubernetes environments |
| Caddy | None (config file) | Yes (automatic) | Limited | Partial | Minimalists, single-binary deployments |
| Raw Nginx + Certbot | None | Manual (cron) | Yes | No | Power users, custom setups |
| HAProxy | Enterprise UI (paid) | Manual | Yes | Partial | High-throughput load balancing |
Prerequisites
Before you begin, make sure you have:
- A VPS running Ubuntu 24.04 LTS with root or sudo access
- SSH access to your server
- A domain name pointing an A record at your VPS IP (required for Let's Encrypt)
- Ports 80, 443, and 81 open on your firewall and cloud provider security group
- At least 1 GB of RAM (NPM itself is lightweight, but the Let's Encrypt renewal job spikes briefly)
- 2-5 GB of free disk space for the NPM container, database, and certificate storage
Recommended Plan: CloudCore Starter>
For a reverse proxy fronting a handful of self-hosted apps, the CloudCore Starter plan is more than enough:>
- 4 vCPU cores
- 8 GB RAM
- 50 GB NVMe SSD
- 32 TB bandwidth
- EUR 7.99/month>
This leaves plenty of headroom to run NPM alongside the backend apps it proxies (Vaultwarden, Nextcloud, Grafana, etc.) all on the same box.
Connect to your server via SSH to get started:
ssh root@your-server-ipStep 1: Update System Packages
Start by updating your package index and upgrading installed packages so you are on the latest kernel and security patches.
sudo apt update && sudo apt upgrade -yExpected output (abbreviated):
Hit:1 http://archive.ubuntu.com/ubuntu noble InRelease
Reading package lists... Done
Building dependency tree... Done
Calculating upgrade... Done
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.If the kernel was updated, reboot before continuing:
sudo rebootStep 2: Install Docker and Docker Compose
Nginx Proxy Manager ships as a Docker image, so you need a working Docker and Docker Compose installation. If you have already set these up, skip to Step 3.
Install Docker Engine using the official convenience script:
curl -fsSL https://get.docker.com | shDocker Compose v2 is included as a plugin with modern Docker installations, so no separate install is required. Verify both are working:
docker --version
docker compose versionExpected output:
Docker version 27.3.1, build ce12230
Docker Compose version v2.29.7If you prefer a manual install or need more detail (rootless mode, alternative distros, post-install hardening), see our full walkthroughs:
Add your current user to thedocker group so you can run commands without sudo:sudo usermod -aG docker $USER
newgrp dockerConfirm Docker is running:
sudo systemctl status dockerStep 3: Create the Docker Compose File
Create a dedicated directory for your NPM deployment. Keeping it in /opt is a common convention and keeps your home directory clean.
sudo mkdir -p /opt/npm
cd /opt/npmCreate the docker-compose.yml file:
sudo nano docker-compose.ymlPaste the following configuration. This uses the official jc21/nginx-proxy-manager image and the optional MariaDB backend (recommended for production over the built-in SQLite):
services: app: image: 'jc21/nginx-proxy-manager:latest' container_name: npm-app restart: unless-stopped ports: - '80:80' # HTTP traffic - '443:443' # HTTPS traffic - '81:81' # Admin UI environment: DB_MYSQL_HOST: "db" DB_MYSQL_PORT: 3306 DB_MYSQL_USER: "npm" DB_MYSQL_PASSWORD: "CHANGE_ME_STRONG_PASSWORD" DB_MYSQL_NAME: "npm" DISABLE_IPV6: 'true' volumes: - ./data:/data - ./letsencrypt:/etc/letsencrypt depends_on: - db
db: image: 'jc21/mariadb-aria:latest' container_name: npm-db restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: 'CHANGE_ME_ROOT_PASSWORD' MYSQL_DATABASE: 'npm' MYSQL_USER: 'npm' MYSQL_PASSWORD: 'CHANGE_ME_STRONG_PASSWORD' MARIADB_AUTO_UPGRADE: '1' volumes: - ./mysql:/var/lib/mysql
Save and exit (Ctrl+O, Enter, Ctrl+X).
About the Three Ports
- Port 80 — Public HTTP. NPM listens here to serve unencrypted traffic, handle redirects to HTTPS, and complete HTTP-01 Let's Encrypt challenges.
- Port 443 — Public HTTPS. All your proxied apps are served through this port with SSL.
- Port 81 — Admin UI. This is the web dashboard where you configure everything. Never expose this port publicly without protection — lock it down with a firewall rule or put it behind an allowlist.
SQLite vs. MariaDB
NPM can run with a built-in SQLite database for simple deployments — just remove the db service and the DB_MYSQL_* environment variables, and NPM defaults to SQLite stored in ./data/database.sqlite. SQLite works fine for small homelabs with a handful of hosts. For anything production-facing, or if you plan to manage more than ~20 proxy hosts, use the MariaDB setup above. It performs better under concurrent writes and is easier to back up cleanly.
Replace both CHANGE_ME_* passwords before starting the stack. Use a strong random generator:
openssl rand -base64 24Step 4: Start Nginx Proxy Manager
From /opt/npm, bring the stack up:
sudo docker compose up -dExpected output:
[+] Running 3/3
✔ Network npm_default Created
✔ Container npm-db Started
✔ Container npm-app StartedThe first startup takes 30-60 seconds as NPM initializes the database schema. Watch the logs to confirm everything is healthy:
sudo docker compose logs -f appYou should see lines like:
[4/16/2026] [10:00:00 AM] [Global ] › ✖ migrate DB
[4/16/2026] [10:00:05 AM] [Migrate ] › ℹ Current database version: 20240427161436
[4/16/2026] [10:00:05 AM] [Setup ] › ℹ Logrotate Timer initialized
[4/16/2026] [10:00:05 AM] [IP Ranges ] › ℹ Fetching IP Ranges from online services...
[4/16/2026] [10:00:07 AM] [Global ] › ✔ Backend PID 247 listening on port 3000 ...Press Ctrl+C to stop tailing once you see the "listening on port 3000" line.
Open the admin UI in your browser:
http://your-server-ip:81Step 5: Log In and Complete the First-Time Wizard
The default credentials for the first login are:
- Email:
[email protected] - Password:
changeme
After saving, you land on the NPM dashboard. The main navigation has four sections:
- Hosts — Proxy Hosts, Redirection Hosts, Streams, 404 Hosts
- SSL Certificates — Let's Encrypt and custom certs
- Access Lists — IP and basic-auth rules reusable across hosts
- Audit Log — Every change made in the UI, with timestamp and user
Step 6: Add Your First Proxy Host
This is where the magic happens. Say you have a Grafana instance running on the same VPS at 127.0.0.1:3000 and you want it accessible at https://grafana.yourdomain.com.
First, make sure the DNS A record for grafana.yourdomain.com points to your VPS public IP. DNS propagation can take anywhere from seconds to a couple of hours depending on your provider and TTL.
In the NPM UI:
grafana.yourdomain.com (hit Enter to add)
- Scheme: http
- Forward Hostname / IP: 127.0.0.1 (or the container name if NPM is on the same Docker network as Grafana)
- Forward Port: 3000
- Enable Cache Assets, Block Common Exploits, and Websockets Support (the last one is important — without it, real-time features in Grafana, Home Assistant, Portainer, etc. will not work)
Forwarding to Other Docker Containers
If your backend service runs in a Docker container on the same host, you have two good options:
- Option A: Put them on the same Docker network. Add
networks: - npm_defaultto the backend's compose file and use the container name as the forward hostname (e.g.,grafanainstead of127.0.0.1). This is cleaner and more resilient to IP changes. - Option B: Bind the backend to
127.0.0.1:PORTon the host and usehost.docker.internalor the Docker bridge IP (typically172.17.0.1) as the forward hostname. NPM's container needsextra_hosts: - "host.docker.internal:host-gateway"added to the compose file for this to work reliably.
Step 7: Request a Let's Encrypt Certificate
With the proxy host saved, edit it again and jump to the SSL tab.
- SSL Certificate: Choose
Request a new SSL Certificate - Enable Force SSL, HTTP/2 Support, and HSTS Enabled
- Email Address for Let's Encrypt: your real email (for expiry notifications)
- Agree to the Let's Encrypt Terms of Service
- Click Save
Visit https://grafana.yourdomain.com — you should see the Grafana login page served over HTTPS with a valid cert.
Wildcard Certificates via DNS-01
For a wildcard certificate like *.yourdomain.com, use the DNS-01 challenge. In the same SSL tab:
For Cloudflare, the credentials look like:
dns_cloudflare_api_token=your_cloudflare_api_token_hereCreate the token in the Cloudflare dashboard with Zone:DNS:Edit permissions scoped to the domain you want to issue certs for.
Once saved, NPM issues the wildcard cert and you can attach it to any proxy host for that domain without needing a separate HTTP-01 challenge per subdomain.
Common SSL Pitfalls
- Rate limits — Let's Encrypt allows 5 failed validations per hostname per hour and 50 certs per registered domain per week. If you are testing, use the staging environment first (not currently exposed in the NPM UI — swap to it via a custom certificate workflow if you need it).
- DNS not propagated — If the A record was changed within the last hour, Let's Encrypt's validation servers may still see the old value. Wait and retry.
- Port 80 blocked — HTTP-01 requires port 80 to be publicly reachable. Check your cloud provider security group and UFW rules.
Step 8: Access Lists, Redirection Hosts, and Streams
With the basics working, NPM's more advanced features cover the remaining 20% of reverse proxy use cases.
Access Lists (IP Allowlist + Basic Auth)
Perfect for admin dashboards (Portainer, Grafana admin, Kibana) you want gated behind an IP allowlist or a password prompt.
203.0.113.50 or 192.168.1.0/24). Set "Satisfy Any" to require any one condition, or leave it off to require all.Redirection Hosts
Permanently redirect an old domain to a new one with a 301. Useful for domain migrations, consolidating aliases, or forcing www → apex (or vice versa).
old-domain.comnew-domain.com301 Moved Permanently/blog/post-1 to redirect to the same path on the new domainStreams (TCP/UDP Pass-Through)
For non-HTTP protocols — game servers (Minecraft, Valheim), databases exposed over VPN, SSH bastion hosts, WireGuard on a non-standard port, etc.
Streams require exposing additional ports from the NPM container. Edit /opt/npm/docker-compose.yml and add the port to the app service:
ports:
- '80:80'
- '443:443'
- '81:81'
- '25565:25565' # Minecraft streamThen sudo docker compose up -d to recreate the container with the new port mapping.
404 Host
Catch-all for any hostname that hits your server but is not configured as a proxy host. Good for presenting a clean "not found" page instead of a default Nginx welcome screen to bot traffic.
Custom Nginx Config Per Host
Every proxy host has an Advanced tab where you can paste arbitrary Nginx directives that get merged into the generated config. Use this for things like:
client_max_body_size 100m;for large file uploads (Nextcloud, photo upload apps)proxy_read_timeout 600s;for long-polling or slow backends- Custom
add_headerrules (CSP, Permissions-Policy) - Rate limiting:
limit_req zone=mylimit burst=20 nodelay;
Step 9: Backup, Upgrade, and Security Hardening
Backing Up NPM
All NPM state lives in three directories under /opt/npm:
./data— application state, SQLite DB (if used), custom Nginx snippets./letsencrypt— issued certificates and account keys./mysql— MariaDB data (if you used the MariaDB setup)
sudo tar -czvf /root/npm-backup-$(date +%Y%m%d).tar.gz \
-C /opt/npm docker-compose.yml data letsencrypt mysqlSchedule it daily via cron and ship the tarball to off-box storage (S3, Backblaze B2, rsync to another VPS). To restore, stop the stack, extract the tarball into a fresh /opt/npm, and bring it back up — everything resumes exactly where it was, cert renewals included.
Upgrading NPM
NPM publishes frequent releases. Upgrade by pulling the latest image and recreating the containers:
cd /opt/npm
sudo docker compose pull
sudo docker compose up -dDocker Compose keeps your named volumes, so no data is lost. Always take a backup first.
Security Hardening
Nginx Proxy Manager is a powerful tool with complete control over your public web surface. Treat the admin UI like the root shell it effectively is.
- Never expose port 81 publicly — If you must reach the admin UI remotely, either:
sudo ufw allow from 203.0.113.50 to any port 81 && sudo ufw deny 81
- Proxy the admin UI through NPM itself on a random subdomain (e.g., admin-abcd1234.yourdomain.com) with an access list attached, and then firewall off the direct port 81 entirely
- Access it over a VPN (WireGuard, Tailscale) or SSH tunnel
- Use a strong admin password — 20+ characters, generated, not reused. Store it in a password manager.
- Enable 2FA on your DNS provider and email — NPM's recovery flow ultimately depends on email access and DNS control.
- Put NPM behind Cloudflare — Optional but valuable. Cloudflare shields your origin IP, provides DDoS mitigation, and lets you enforce access rules at the edge. Enable "Full (strict)" SSL mode so Cloudflare still validates NPM's Let's Encrypt cert.
- Keep Docker and the host patched —
sudo apt update && sudo apt upgrade -yweekly, anddocker compose pullto stay current on NPM itself. - Configure UFW — Allow only what you need:
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw deny 81
sudo ufw enableFor deeper Nginx-specific tuning and hardening tips, see our companion article: How to Install and Configure Nginx on Ubuntu.
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| Let's Encrypt request fails with "too many certificates already issued" | Hit the 50 certs/week/domain rate limit | Wait up to 7 days. Use a wildcard cert (DNS-01) to cover all subdomains with a single issuance. |
| Let's Encrypt fails with "Invalid response from http://..." | Port 80 is blocked, DNS points elsewhere, or another service is binding port 80 | Verify curl http://yourdomain.com/.well-known/acme-challenge/test from outside the server reaches NPM. Check firewall, cloud security group, and DNS A record. |
502 Bad Gateway on a proxy host | Upstream backend is unreachable, offline, or bound to the wrong interface | docker ps to confirm the backend is running. From inside NPM container: docker exec npm-app curl -I http://backend:port. Check the forward hostname — localhost/127.0.0.1 from inside the container is the container itself, not the host. |
| Websocket connections fail (real-time features break) | "Websockets Support" toggle disabled on the proxy host | Edit the proxy host → Details tab → enable Websockets Support → Save. |
| Admin UI at port 81 not loading | UFW blocking, wrong port mapping, or container crashed | sudo docker compose ps to confirm npm-app is up. sudo ufw status. sudo docker compose logs app. |
| Certificate renewed but browser shows old cert | Nginx did not reload after renewal | Rare — normally NPM reloads automatically. Force it: sudo docker exec npm-app nginx -s reload |
| Forgot admin password | Locked out of UI | Reset directly in the DB. For MariaDB: docker exec -it npm-db mysql -u root -p then USE npm; UPDATE user SET is_disabled = 0 WHERE email='[email protected]'; and reset via the password reset flow. |
| "Internal Error" on saving a host | Invalid custom Nginx config in Advanced tab | Remove the Advanced config snippet, save, then re-add one directive at a time to isolate the bad line. |
Viewing Logs
Stream NPM's logs in real time:
cd /opt/npm
sudo docker compose logs -f appNginx access and error logs live inside the container:
sudo docker exec npm-app tail -f /data/logs/default-host_access.log
sudo docker exec npm-app tail -f /data/logs/fallback_error.logPer-host logs are in /data/logs/proxy-host-<id>_access.log and _error.log.
FAQ
Is Nginx Proxy Manager free?
Yes. NPM is fully open source under the MIT license. There is no paid tier, no feature gate, and no phone-home telemetry. The only costs are your VPS and (optionally) a domain name.
Can I use Nginx Proxy Manager without Docker?
Not officially. NPM is designed and distributed as a Docker image, and the project does not support bare-metal installs. If you strongly prefer native Nginx, use raw Nginx with Certbot instead — see our Nginx install guide.
Does NPM work with Cloudflare's orange cloud (proxied DNS)?
Yes, with one caveat. When Cloudflare proxies your domain, the HTTP-01 Let's Encrypt challenge can fail because Cloudflare terminates the challenge connection. Use the DNS-01 challenge with Cloudflare's API token instead — NPM has built-in support and it works flawlessly with proxied records.
How many proxy hosts can one NPM instance handle?
In practice, several hundred. Nginx itself scales to thousands of virtual hosts with minimal overhead. The real limits are your VPS RAM (each proxied connection uses a small buffer) and the NPM admin UI responsiveness, which starts to feel sluggish past ~500 hosts. For homelabs and small-to-mid SaaS, you will never hit those limits.
Can I run multiple NPM instances for high availability?
Yes, but it takes some care. Run two NPM containers on separate VPS instances, point them at the same external MariaDB (with replication), and use a simple DNS round-robin or a TCP load balancer (HAProxy, cloud provider LB) in front. Certificate storage needs to be shared — use a network filesystem (NFS, GlusterFS) or sync ./letsencrypt between nodes. For most users, this is overkill; a single well-backed-up NPM is more than enough.
What happens when my VPS reboots?
The NPM containers are set to restart: unless-stopped in the compose file, so they come back up automatically on boot. All certificates, hosts, and settings persist in the mounted volumes. You should be online within 10-20 seconds of the server finishing boot.
Next Steps
With Nginx Proxy Manager handling your reverse proxy, SSL, and access control, you have a solid foundation for self-hosting. Recommended next steps:
- Deploy your first self-hosted app behind NPM — Vaultwarden, Nextcloud, or Home Assistant all pair beautifully with NPM. Spin them up in Docker, add a proxy host, request a cert, done.
- Set up monitoring with Uptime Kuma — Uptime Kuma runs in a single Docker container, fronted by NPM, and monitors every endpoint you publish. Get Telegram/Discord/email alerts if any host goes down.
- Add Authelia or Authentik for SSO — NPM's basic auth is fine for personal use, but if you want unified single sign-on across all your self-hosted apps, drop in Authelia or Authentik. Both integrate with NPM via forward-auth.
- Automate backups with Restic or BorgBackup — Schedule nightly backups of
/opt/npmto off-site storage and test restores monthly. - Read the official docs — The Nginx Proxy Manager documentation covers advanced topics like custom DNS providers, offline Docker installs, and multi-stage proxy chains.
Skip the Manual Install — Get NPM Pre-Installed>
Our CloudCore Starter VPS plans come with Nginx Proxy Manager one-click deployable: Docker, NPM, MariaDB, and UFW rules configured, a generated admin password in your welcome email, and ports 80/443/81 already locked down correctly.>
- NPM + MariaDB running in Docker Compose
- Admin UI protected by firewall rule (your IP only)
- UFW and fail2ban pre-configured
- Automated weekly backups of /opt/npm included
- Optional wildcard cert setup with your DNS provider>
Deploy Your CloudCore Starter VPS Now — Plans start at EUR 7.99/month.