Nginx Proxy Manager TLS Configuration: SSL Certificates, Let’s Encrypt, and Custom Certificates

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:

  1. Writes an nginx virtual host configuration.
  2. Places certificate files on disk.
  3. Configures nginx to use those files.
  4. 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

  1. Open NPM at http://your-server:81.
  2. Go to SSL Certificates → Add SSL Certificate.
  3. Choose Let’s Encrypt.
  4. 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
  1. 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:

  1. Go to Proxy Hosts → Add Proxy Host.
  2. 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.100 or backend-container-name
  • Forward Port: 8080
  1. 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
  1. 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:

  1. Go to SSL Certificates → Add SSL Certificate.
  2. Choose Custom (not Let’s Encrypt).
  3. 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)
  1. 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

ProblemCauseFix
Let’s Encrypt fails: timeout during connectPort 80 not reachableOpen port 80 in firewall; verify DNS
DNS challenge failsWrong API credentialsRe-enter DNS provider credentials
Certificate is not valid yetClock skew on the serverSync NTP: timedatectl set-ntp true
Proxy host shows red indicatorBackend not reachableCheck backend IP/port; check network connectivity
Custom certificate shows as expiredWrong certificate uploadedRe-upload with the correct full-chain certificate
Admin UI inaccessible after upgradeData directory permission changeCheck Docker volume ownership

Migrating from Apache/nginx to NPM

If you have existing Let’s Encrypt certificates:

  1. Copy the certificate files to the NPM Let’s Encrypt volume:
   docker cp /etc/letsencrypt npm-container:/etc/letsencrypt
  1. Import via the NPM UI using the Custom Certificate option, pasting the fullchain.pem and privkey.pem contents.
  2. 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.

Scroll to Top