Nginx Proxy Manager (NPM) is a web-based GUI for nginx reverse proxy configuration. It manages SSL certificates through a visual interface, which makes it accessible to administrators who are not comfortable editing nginx configuration files directly. This article covers setting up Let’s Encrypt certificates in NPM, uploading custom certificates, configuring advanced TLS settings, and understanding where NPM stores its certificate data.
How Nginx Proxy Manager handles TLS
NPM stores its configuration and certificates in a SQLite (or MySQL) database and on disk under /data/ (Docker) or its configured data path. When you configure a Proxy Host with SSL, NPM:
- Writes an nginx virtual host configuration.
- Places certificate files on disk.
- Configures nginx to use those files.
- For Let’s Encrypt, runs certbot in the background and stores the certificate.
You configure everything through the web UI at port 81.
Installation with Docker Compose
The standard NPM deployment:
version: '3.8'
services:
app:
image: jc21/nginx-proxy-manager:latest
ports:
- "80:80"
- "443:443"
- "81:81" # Admin UI — restrict to internal network only in production
volumes:
- npm_data:/data
- npm_letsencrypt:/etc/letsencrypt
environment:
# Optional: use MySQL instead of SQLite
# DB_MYSQL_HOST: db
# DB_MYSQL_PORT: "3306"
# DB_MYSQL_USER: npm
# DB_MYSQL_PASSWORD: npm-password
# DB_MYSQL_NAME: npm
volumes:
npm_data:
npm_letsencrypt:
Security note: Port 81 (admin UI) should not be exposed to the internet. Use a firewall rule to restrict it to your management network, or put it behind a VPN.
Obtaining a Let’s Encrypt certificate through the UI
- Open NPM at
http://your-server:81. - Go to SSL Certificates → Add SSL Certificate.
- Choose Let’s Encrypt.
- Fill in:
- Domain Names: enter all domains the certificate should cover (e.g.,
example.com www.example.com) - Email Address: your email for Let’s Encrypt expiry notifications
- Use a DNS Challenge: enable for wildcard certificates or when port 80 is not available
- Agree to Terms of Service: check this
- Click Save.
NPM calls certbot in the background. If the DNS record points to your server and port 80 is reachable, the certificate is issued in seconds.
For wildcard certificates with DNS challenge, select your DNS provider from the dropdown and enter the required credentials. NPM supports Cloudflare, Route53, Namecheap, DigitalOcean, and others.
Cloudflare example:
- DNS Provider: Cloudflare
- Propagation Seconds: 30 (or higher for slower DNS)
- Credentials File Content:
dns_cloudflare_api_token = your-cloudflare-api-token
Creating a Proxy Host with TLS
After the certificate is issued:
- Go to Proxy Hosts → Add Proxy Host.
- Fill in the Details tab:
- Domain Names:
app.example.com - Scheme:
http(if the backend does not use TLS) - Forward Hostname/IP:
192.168.1.100orbackend-container-name - Forward Port:
8080
- Switch to the SSL tab:
- SSL Certificate: select the Let’s Encrypt certificate you just created
- Force SSL: enable to redirect HTTP to HTTPS
- HTTP/2 Support: enable
- HSTS Enabled: enable (adds Strict-Transport-Security header)
- HSTS Subdomains: enable if you want
includeSubDomains
- Click Save.
NPM writes the nginx configuration and reloads nginx automatically.
Uploading a custom certificate
For certificates from an internal CA or a commercial provider:
- Go to SSL Certificates → Add SSL Certificate.
- Choose Custom (not Let’s Encrypt).
- Provide:
- Certificate: paste or upload the full certificate chain (leaf + intermediates)
- Certificate Key: paste or upload the private key
- Intermediate Certificate: (optional, you can include this in the Certificate field instead)
- Click Save.
The certificate is uploaded to NPM’s database and stored on disk. Use it by selecting it in any Proxy Host’s SSL tab.
Advanced TLS settings per Proxy Host
After creating a Proxy Host, the SSL tab includes Advanced settings. Click Advanced to add custom nginx directives:
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Frame-Options SAMEORIGIN always;
add_header X-Content-Type-Options nosniff always;
Advanced settings are appended to the nginx server block, so you can add any valid nginx directive.
Where NPM stores certificate files
Inside the Docker volume (or /data/ on bare metal):
/data/
├── nginx/
│ ├── proxy_host/ ← Generated nginx configs
│ └── ssl/ ← Symlinks to certificate files
├── custom_ssl/ ← Custom (non-ACME) certificates
└── letsencrypt/ ← Certbot certificate storage
└── live/
└── example.com/
├── cert.pem
├── chain.pem
├── fullchain.pem
└── privkey.pem
Accessing NPM certificates from other containers
If other services in the same Docker Compose stack need the certificates (e.g., a Postfix mail server):
services:
mailserver:
image: docker.io/mailserver/docker-mailserver:latest
volumes:
- npm_letsencrypt:/etc/letsencrypt:ro
environment:
SSL_TYPE: letsencrypt
SSL_DOMAIN: mail.example.com
Or copy certificates using a shared volume and a post-renewal script.
API access to NPM (automation)
NPM has an undocumented REST API. Authenticate and manage certificates programmatically:
# Get an API token
TOKEN=$(curl -s -X POST http://localhost:81/api/tokens \
-H "Content-Type: application/json" \
-d '{"identity":"admin@example.com","secret":"changeme"}' \
| python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")
# List SSL certificates
curl -H "Authorization: ******" \
http://localhost:81/api/nginx/certificates | python3 -m json.tool
# Force renewal of a specific certificate
CERT_ID=1
curl -X POST -H "Authorization: ******" \
http://localhost:81/api/nginx/certificates/${CERT_ID}/renew
Automatic Let’s Encrypt renewal
NPM includes a built-in renewal timer. It checks for expiring certificates every day and renews those within 30 days of expiry. No external cron job is needed.
You can verify renewal is working by checking the NPM logs:
docker logs nginx-proxy-manager-app-1 2>&1 | grep -i "renew\|certbot\|letsencrypt"
Or via the API:
curl -H "Authorization: ******" \
http://localhost:81/api/nginx/certificates | \
python3 -c "import sys,json; certs=json.load(sys.stdin); [print(c['nice_name'], c['expires_on']) for c in certs]"
Restricting access to the admin UI in production
The admin UI (port 81) should not be publicly accessible. Options:
Option 1 — Firewall rule (iptables):
# Allow port 81 only from management network
sudo iptables -A INPUT -p tcp --dport 81 -s 10.0.0.0/8 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 81 -j DROP
Option 2 — Change the admin port in docker-compose.yml:
ports:
- "127.0.0.1:81:81" # Only accessible from localhost
Then use SSH port forwarding to access it:
ssh -L 8081:localhost:81 user@server
# Access at http://localhost:8081
Option 3 — Create a Proxy Host for the admin UI with IP restriction in the Advanced tab:
allow 192.168.1.0/24;
deny all;
Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
Let’s Encrypt fails: timeout during connect | Port 80 not reachable | Open port 80 in firewall; verify DNS |
| DNS challenge fails | Wrong API credentials | Re-enter DNS provider credentials |
Certificate is not valid yet | Clock skew on the server | Sync NTP: timedatectl set-ntp true |
| Proxy host shows red indicator | Backend not reachable | Check backend IP/port; check network connectivity |
| Custom certificate shows as expired | Wrong certificate uploaded | Re-upload with the correct full-chain certificate |
| Admin UI inaccessible after upgrade | Data directory permission change | Check Docker volume ownership |
Migrating from Apache/nginx to NPM
If you have existing Let’s Encrypt certificates:
- Copy the certificate files to the NPM Let’s Encrypt volume:
docker cp /etc/letsencrypt npm-container:/etc/letsencrypt
- Import via the NPM UI using the Custom Certificate option, pasting the
fullchain.pemandprivkey.pemcontents. - Reconfigure existing Proxy Hosts to use the imported certificates.
Summary
Nginx Proxy Manager makes TLS management accessible through a web UI. Let’s Encrypt certificates are obtained with a click and renewed automatically. For wildcard certificates, select a DNS provider and provide API credentials. Custom certificates (internal CA or commercial) are uploaded through the Custom Certificate option. Advanced TLS settings (cipher suites, headers, HSTS) are added in the Proxy Host’s Advanced tab. Restrict the admin UI to internal networks to prevent unauthorized access to certificate management.