Introduction
Integrating Nginx Proxy Manager (NPM) with Cloudflare creates a robust architecture for web services. This setup leverages Cloudflare's global CDN for DDoS protection and caching while using NPM for granular reverse proxy control, authentication, and local management.
This guide covers the technical implementation, including Origin Certificate generation, SSL termination, and preserving the Real IP of your visitors—critical for logging and security applications in 2025.
---
Prerequisites
Before proceeding, ensure you have the following:
- Domain Name: Registered and active on Cloudflare (Nameservers updated).
- Server Access: Root or sudo access to your VPS/Dedicated server where NPM is installed.
- Nginx Proxy Manager: Installed and accessible via its Web UI (default port
81). - Docker & Docker Compose: Recommended for easy management of NPM and network configuration.
---
Phase 1: Cloudflare Configuration
Step 1: Create an Origin Certificate
To ensure end-to-end encryption without paying for third-party SSL certificates, Cloudflare allows you to generate a certificate trusted *only* by Cloudflare for your origin.
1. Navigate to the Cloudflare Dashboard > SSL/TLS > Origin Server. 2. Click Create Certificate. 3. Hostnames: Enter your domain (e.g., *.yourdomain.com and yourdomain.com). 4. Validity: Set to 15 years (max). 5. Key Type: Select RSA (2048) for broad compatibility with NPM. 6. Click Create.
Crucial Step: Copy the Origin Certificate content and the Private Key content to a secure text file. You will need these in Phase 2.
Step 2: Adjust DNS and SSL Mode
1. Go to DNS > Records. 2. Ensure your A Record points to your NPM server's IP address. Ensure the proxy status is Proxied (Orange Cloud). If it is DNS Only (Grey Cloud), Cloudflare bypasses your server, exposing your real IP. 3. Navigate to SSL/TLS > Overview. 4. Change the encryption mode to Full (strict). This is mandatory. It tells Cloudflare to validate your NPM server's certificate, which must be the Origin Certificate you just created.
---
Phase 2: Nginx Proxy Manager Configuration
Step 1: Accessing NPM and Adding the Host
1. Log in to your Nginx Proxy Manager Dashboard (usually http://your-server-ip:81). 2. Click Hosts > Proxy Hosts. 3. Click Add Proxy Host. * Domain Names: Enter yourdomain.com (and www.yourdomain.com if desired). * Scheme: http * Forward Hostname / IP: Enter the IP of the *container* or *service* NPM is routing to (e.g., 127.0.0.1 or a separate Docker container IP like 172.18.0.5). * Forward Port: The port your web app listens on (e.g., 3000, 8080, or 80).
Step 2: Configuring the SSL (The Critical Link)
Many users fail here by selecting "Force SSL" without uploading the specific Cloudflare certificate.
1. In the Add Proxy Host window, switch to the SSL tab. 2. Select Custom Certificate. 3. Key: Paste the Private Key you generated in Cloudflare. 4. Certificate: Paste the Origin Certificate content. 5. Force SSL: Toggle this ON. This ensures HTTP requests are redirected to HTTPS. 6. HTTP/2 Support: Toggle ON for modern performance. 7. HSTS Enabled: Toggle ON (Enable for 6 months max-age).
---
Phase 3: Fixing the Real IP Issue
A common pain point when proxying through Cloudflare is that NPM and your upstream applications see every connection coming from Cloudflare's IP range (e.g., 173.245.48.0/20). This breaks access logs and rate limiters.
Option A: NPM Configuration (Recommended)
You need to configure Nginx (underlying NPM) to trust Cloudflare as a proxy.
1. SSH into your server. 2. Edit your NPM Docker Compose file (usually docker-compose.yml) to mount the Nginx configuration or modify the main config file if running bare metal.
If you are using Docker, you can modify the nginx.conf custom mapping.
3. Add the Cloudflare IPs to the set_real_ip_from directive. Since Cloudflare updates IPs, it is best to fetch them dynamically or use a known list.
Create a configuration snippet /etc/nginx/conf.d/cloudflare.conf:
Cloudflare IPv4
set_real_ip_from 173.245.48.0/20; set_real_ip_from 103.21.244.0/22; set_real_ip_from 103.22.200.0/22; set_real_ip_from 103.31.4.0/22; set_real_ip_from 141.101.64.0/18; set_real_ip_from 108.162.192.0/18; set_real_ip_from 190.93.240.0/20; set_real_ip_from 188.114.96.0/20; set_real_ip_from 197.234.240.0/22; set_real_ip_from 198.41.128.0/17; set_real_ip_from 162.158.0.0/15; set_real_ip_from 104.16.0.0/13; set_real_ip_from 104.24.0.0/14; set_real_ip_from 172.64.0.0/13; set_real_ip_from 131.0.72.0/22;
Cloudflare IPv6
set_real_ip_from 2606:4700::/32; set_real_ip_from 2606:4700::/36; set_real_ip_from 2606:4700::/42; set_real_ip_from 2606:4700::/44; set_real_ip_from 2606:4700::/48; set_real_ip_from 2606:4700::/52; set_real_ip_from 2606:4700::/56; set_real_ip_from 2606:4700::/58; set_real_ip_from 2606:4700::/60;
real_ip_header CF-Connecting-IP;
*Note: You must restart your NPM container for these changes to take effect.*
Option B: Cloudflare "True Client IP"
For Enterprise users, Cloudflare offers the "True-Client-IP" header. However, for free plan users, Option A is the correct path. Alternatively, Cloudflare now adds the CF-Connecting-IP header automatically, but the Nginx default configuration ignores it unless set_real_ip_from is set correctly.
---
Phase 4: Automation via API (Advanced)
If you manage dozens of subdomains, manually entering them into NPM is tedious. You can use the Nginx Proxy Manager API combined with the Cloudflare API.
Python Script Example
This Python script demonstrates how to programmatically create a Proxy Host in NPM and update DNS on Cloudflare.
*Note: This requires an existing NPM Custom Certificate ID (for the Cloudflare Origin Cert). You can find this ID by inspecting the browser network tab while loading the NPM SSL Certificates page.*
import requests
import json
Configuration
NPM_URL = "http://localhost:81/api" NPM_USER = "admin@example.com" NPM_PASS = "password" CLOUDFLARE_API_TOKEN = "YOUR_CLOUDFLARE_API_TOKEN" DOMAIN = "newsite.yourdomain.com" FORWARD_IP = "192.168.1.50" FORWARD_PORT = "80" CUSTOM_CERT_ID = 1 # Replace with the ID of your uploaded Cloudflare Origin Cert
1. Get NPM Token
session = requests.Session() login_payload = {"identity": NPM_USER, "secret": NPM_PASS} login = session.post(f"{NPM_URL}/tokens", json=login_payload) token = login.json()["data"]["token"]
session.headers.update({"Authorization": f"Bearer {token}"})
2. Create Proxy Host in NPM
proxy_payload = { "domain_names": [DOMAIN], "forward_scheme": "http", "forward_host": FORWARD_IP, "forward_port": int(FORWARD_PORT), "certificate_id": CUSTOM_CERT_ID, "ssl_forced": True, "http2_support": True, "meta": {"letsencrypt_agree": False, "dns_challenge": False} }
response = session.post(f"{NPM_URL}/nginx/proxy-hosts", json=proxy_payload)
if response.status_code == 201: print(f"[+] NPM Proxy Host created for {DOMAIN}") else: print(f"[-] Error creating NPM host: {response.text}") exit()
3. Create DNS Record on Cloudflare
zone_id = "YOUR_ZONE_ID"
dns_payload = { "type": "A", "name": DOMAIN, "content": "YOUR_NPM_SERVER_PUBLIC_IP", "ttl": 1, "proxied": True }
headers = { "Authorization": f"Bearer {CLOUDFLARE_API_TOKEN}", "Content-Type": "application/json" }
cf_response = requests.post( f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records", headers=headers, json=dns_payload )
if cf_response.status_code == 200: print(f"[+] Cloudflare DNS record created for {DOMAIN}") else: print(f"[-] Error creating DNS record: {cf_response.text}")
---
Comparison: Cloudflare vs. Default NPM Setup
| Feature | NPM Default (Let's Encrypt) | NPM + Cloudflare (Origin Cert) | | :--- | :--- | :--- | | SSL Validity | 90 Days (Auto-renew) | 15 Years (Manual update) | | DDoS Protection | Server Level only | Enterprise Edge Level | | Global Caching | None | High (Assets cached at edge) | | IP Visibility | Real IP exposed (Can leak) | Real IP Hidden (Proxied) | | Setup Complexity | Low | Medium | | Cost | Free | Free (Standard Plan) |
Troubleshooting Common Errors
Error: 522 Connection Timed Out
ufw or iptables).0.0.0.0/0). Even though NPM may run on port 81, the *Proxy Host* usually listens on 80/443 externally.Error: 520 Web Server Returned an Unknown Error
Error: Too Many Redirects
Conclusion
Setting up Nginx Proxy Manager with Cloudflare provides a free, enterprise-grade solution for web traffic management. By utilizing the Origin Server Certificate, you ensure valid SSL encryption without the latency of Let's Encrypt handshakes on the edge, and by properly configuring Real IP headers, you retain full visibility into your traffic analytics. This hybrid approach is ideal for self-hosted applications in 2025.