Deploying WordPress behind Nginx Proxy Manager requires understanding how reverse proxy layers interact with DNS routing and SSL termination. Misconfigured proxy chains produce redirect loops, failed certificate validation, or exposed origin IPs. This guide covers a production Docker Compose stack and routing architecture decisions for self-hosted WordPress.
Prerequisites
The following items are required:
- Ubuntu server with Docker Engine and Docker Compose installed
- Ports 80 and 443 open in UFW
- Domain managed in Cloudflare DNS
- Basic familiarity with Docker networking and reverse proxies
Routing Architecture Options
External traffic can reach a self-hosted WordPress instance through three primary methods. Each approach has distinct security, infrastructure, and control characteristics.
Direct to Nginx Proxy Manager
Traffic hits the server public IP directly on ports 80 and 443. Nginx Proxy Manager handles Let’s Encrypt certificate issuance via HTTP-01 challenges and routes requests to the WordPress container.
This approach provides complete data sovereignty. No third party has access to decrypted traffic or request metadata. The server IP address is publicly visible through DNS lookups. Volumetric DDoS attacks target the network interface directly without intermediate filtering.
Cloudflare DNS Proxy with Nginx Proxy Manager
Traffic routes through Cloudflare edge nodes before reaching the origin server. Cloudflare proxies requests to the server public IP. Nginx Proxy Manager handles internal container routing and local SSL certificate management.
This configuration provides DDoS mitigation, edge caching, and IP masking without monthly fees. The server must accept connections on ports 80 and 443. Cloudflare SSL mode must be set to Full (strict) to prevent redirect loops. Both Cloudflare and Nginx Proxy Manager terminate SSL, creating two encryption layers.
Cloudflare Tunnel Direct to Container
The cloudflared daemon runs in Docker and establishes an outbound connection to Cloudflare infrastructure. Cloudflare forwards HTTP traffic directly to the WordPress container port 80. No inbound ports are required on the server firewall.
This approach works through CGNAT and strict network environments. It bypasses local reverse proxy routing rules entirely. Certificate management and access logging are handled by Cloudflare rather than local infrastructure.
Recommended Configuration for Dedicated Servers
For a dedicated server with a static public IP, Cloudflare DNS Proxy combined with Nginx Proxy Manager provides the best balance. Cloudflare handles DDoS protection and caching while hiding the origin IP. Nginx Proxy Manager provides local control over SSL certificates, custom headers, rate limiting, and routing for multiple containers.
For home connections behind CGNAT or environments requiring zero open inbound ports, route Cloudflare Tunnel output to Nginx Proxy Manager internally. This preserves local routing control without exposing ports to the public internet.
Deploy Docker Compose Stack
Create a directory for the WordPress deployment:
mkdir -p /opt/containers/wordpress-stack
cd /opt/containers/wordpress-stackCreate a docker-compose.yml file with the following content:
version: '3.8'
networks:
npm-network:
driver: bridge
services:
npm:
image: 'jc21/nginx-proxy-manager:latest'
container_name: npm
restart: always
ports:
- '80:80'
- '81:81'
- '443:443'
volumes:
- ./npm/data:/data
- ./npm/letsencrypt:/etc/letsencrypt
networks:
- npm-network
mariadb:
image: mariadb:11
container_name: wordpress-db
restart: always
environment:
MYSQL_ROOT_PASSWORD: 'ChangeThisSuperSecureRootPassword'
MYSQL_DATABASE: 'wordpress_db'
MYSQL_USER: 'wp_user'
MYSQL_PASSWORD: 'ChangeThisUserPassword'
volumes:
- ./db_data:/var/lib/mysql
networks:
- npm-network
wordpress:
image: wordpress:latest
container_name: wordpress-app
restart: always
depends_on:
- mariadb
environment:
WORDPRESS_DB_HOST: mariadb:3306
WORDPRESS_DB_USER: wp_user
WORDPRESS_DB_PASSWORD: 'ChangeThisUserPassword'
WORDPRESS_DB_NAME: wordpress_db
volumes:
- ./wp_data:/var/www/html
networks:
- npm-networkThis configuration creates three containers on a shared bridge network. Nginx Proxy Manager exposes ports 80, 443, and 81. Port 81 provides access to the NPM admin interface. MariaDB and WordPress communicate over the internal Docker network without exposed ports.
Start the stack:
docker compose up -dVerify all containers are running:
docker psConfigure DNS Records
Log into the Cloudflare Dashboard and navigate to DNS Records for the target domain. Add or update an A record:
- Type: A
- Name: @ (for root domain) or desired subdomain
- IPv4 address: Server public IP
- Proxy status: Proxied (orange cloud) for Cloudflare protection, DNS only (grey cloud) for direct routing
- TTL: Auto
DNS propagation typically completes within minutes but can take up to 48 hours in some cases.
Configure Nginx Proxy Manager
Access the NPM admin interface at http://SERVER_IP:81. Log in with default credentials:
- Email: admin@example.com
- Password: changeme
Update the admin email and password when prompted.
Create Proxy Host
Navigate to Hosts, then Proxy Hosts, then Add Proxy Host. Configure the Details tab:
- Domain Names: example.com www.example.com
- Scheme: http
- Forward Hostname / IP: wordpress-app
- Forward Port: 80
- Enable Block Common Exploits
- Enable Websockets Support if required
The Forward Hostname uses the Docker container name. Docker’s internal DNS resolves container names to their bridge network IP addresses.
Configure the SSL tab:
- SSL Certificate: Request a new SSL Certificate
- Enable Force SSL
- Enable HTTP/2 Support
- Agree to Let’s Encrypt Terms of Service
Save the configuration. Nginx Proxy Manager will request a Let’s Encrypt certificate using the HTTP-01 challenge method.
Configure Cloudflare SSL Settings
If using Cloudflare DNS Proxy (orange cloud), configure SSL/TLS settings in the Cloudflare Dashboard. Navigate to SSL/TLS, then Overview. Set encryption mode to Full (strict).
This setting ensures end-to-end encryption between the client browser, Cloudflare edge, and origin server. Flexible mode terminates SSL at the Cloudflare edge and uses unencrypted HTTP to the origin. Full mode allows self-signed certificates on the origin. Full (strict) requires valid certificates on both Cloudflare and the origin server.
Complete WordPress Installation
Navigate to the configured domain in a web browser. The WordPress installation screen should appear. Select language, configure site title, and create an administrator account.
If the page does not load, check the following:
- Verify DNS resolution points to the correct IP address using dig or nslookup
- Check container logs with docker logs wordpress-app and docker logs npm
- Confirm UFW allows ports 80 and 443
- Verify Cloudflare SSL mode is set to Full (strict) if using Cloudflare Proxy
Troubleshooting Common Issues
ERR_TOO_MANY_REDIRECTS
This error occurs when Cloudflare SSL mode is set to Flexible while Nginx Proxy Manager forces HTTPS. Cloudflare receives HTTPS redirects from the origin but sends HTTP requests, creating an infinite loop. Set Cloudflare SSL mode to Full (strict).
Certificate Validation Failures
Let’s Encrypt HTTP-01 challenges require port 80 to be accessible from the public internet. Cloudflare Proxy must allow HTTP traffic to pass through for initial certificate issuance. After the certificate is issued, Force SSL can redirect HTTP to HTTPS.
Container Name Resolution Failures
If Nginx Proxy Manager cannot resolve the wordpress-app hostname, verify both containers are on the same Docker network. Check network membership with docker network inspect wordpress-stack_npm-network.
AI assistance is used on this site for language, formatting, and turning research into a consistent template. It is not used to perform the underlying research or verify technical claims. Every command, configuration, and step in this post is tested by hand before publication.