| |

WordPress Behind Nginx Reverse Proxy with SSL Termination

Whenever you run a WordPress website behind a reverse proxy with SSL termination, you encounter a fundamental configuration challenge: the reverse proxy handles HTTPS connections from clients but forwards plain HTTP traffic to the WordPress backend. This creates a mismatch where WordPress, configured with HTTPS URLs (e.g., https://yourdomain.com), receives HTTP requests and becomes confused about the actual protocol being used.

This configuration mismatch typically results in redirect loops, mixed content warnings, broken admin functionality, and WordPress health check failures. This guide provides the complete solution to properly configure both the reverse proxy and WordPress backend to work seamlessly together.

Prerequisites:

  • Functioning Nginx reverse proxy server with SSL certificates and basic configuration
  • WordPress installation on backend server (Nginx or Apache)
  • WordPress configured with HTTPS site URL

The Problem

Client (HTTPS) → Reverse Proxy (SSL termination) → WordPress Backend (HTTP)

What happens:

  1. Client connects to https://yourdomain.com
  2. Reverse proxy terminates SSL, forwards HTTP to WordPress backend
  3. WordPress thinks it’s being accessed via HTTP but is configured for HTTPS
  4. WordPress generates incorrect URLs or redirects, causing loops and mixed content
  5. Result: Site broken, admin inaccessible, or security warnings

Solution Strategy

The solution involves a coordinated configuration between three components: the reverse proxy must send the correct headers indicating the original HTTPS request, the backend web server must be configured to recognize and process these headers, and WordPress must be configured to detect the SSL status from the forwarded headers rather than the direct connection.

The key is using HTTP headers like X-Forwarded-Proto to communicate the original request protocol from the reverse proxy to WordPress, while also ensuring real client IP addresses are preserved for logging and security purposes. This approach maintains the security benefits of SSL while allowing the backend to operate over HTTP.

Step 1: Configure Nginx Reverse Proxy Location Block

In your reverse proxy’s server block, configure the location directive with the essential headers:

location / {
    # Forward to WordPress backend server
    proxy_pass http://10.0.10.100:80;  # Replace with your WordPress server IP
    
    # Essential headers for SSL termination
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;     # Critical for HTTPS detection
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Port $server_port;
    
    # Connection optimization
    proxy_set_header Connection "";
    proxy_http_version 1.1;
    
    # WordPress-specific settings
    client_max_body_size 100M;      # File upload limit
    proxy_connect_timeout 60s;
    proxy_send_timeout 60s;
    proxy_read_timeout 300s;        # Important for admin operations
    
    # Performance optimization
    proxy_buffering on;
    proxy_buffer_size 4k;
    proxy_buffers 8 4k;
    proxy_busy_buffers_size 8k;
    
    # Preserve original redirects
    proxy_redirect off;
}

Critical Headers Explained:

  • X-Forwarded-Proto $scheme – Tells backend the original protocol (https)
  • X-Real-IP $remote_addr – Original client IP for logging and security
  • X-Forwarded-For $proxy_add_x_forwarded_for – Complete IP chain for real IP detection
  • X-Forwarded-Host $host – Original domain name requested

Step 2: Configure WordPress Backend Server

For Nginx Backend

Add this to your WordPress server’s Nginx configuration:

server {
    listen 80;
    server_name yourdomain.com;
    root /var/www/html;
    index index.php index.html;
    
    # Enable real IP logging from reverse proxy
    set_real_ip_from 10.0.10.1;      # Your reverse proxy IP
    real_ip_header X-Forwarded-For;
    real_ip_recursive on;
    
    # Custom log format to capture real client IPs
    log_format real_ip '$remote_addr - $remote_user [$time_local] '
                      '"$request" $status $body_bytes_sent '
                      '"$http_referer" "$http_user_agent" '
                      '"$http_x_forwarded_for"';
    
    access_log /var/log/nginx/yourdomain.com-access.log real_ip;
    
    location / {
        try_files $uri $uri/ /index.php?$args;
    }
    
    location ~ \.php$ {
        # Pass reverse proxy headers to PHP
        fastcgi_param HTTPS $https if_not_empty;
        fastcgi_param HTTP_X_FORWARDED_PROTO $http_x_forwarded_proto;
        fastcgi_param HTTP_X_FORWARDED_FOR $http_x_forwarded_for;
        fastcgi_param HTTP_X_REAL_IP $http_x_real_ip;
        
        # Standard PHP-FPM configuration
        fastcgi_pass unix:/run/php/php8.4-fpm.sock;;  # Adjust for your setup
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

For Apache Backend

Add this to your VirtualHost configuration:

<VirtualHost *:80>
    ServerName yourdomain.com
    DocumentRoot /var/www/html
    
    # Enable required modules (mod_remoteip, mod_setenvif)
    # a2enmod remoteip setenvif
    
    # Handle reverse proxy headers BEFORE any processing
    SetEnvIf X-Forwarded-Proto "^https$" HTTPS=on
    SetEnvIf X-Forwarded-Proto "^https$" SERVER_PORT=443
    SetEnvIf X-Forwarded-Host "^(.+)$" HTTP_HOST=$1
    
    # Real IP detection for logging
    RemoteIPHeader X-Forwarded-For
    RemoteIPTrustedProxy 10.0.10.1    # Your reverse proxy IP
    RemoteIPInternalProxy 10.0.10.1
    
    # Custom log format to show real client IPs
    LogFormat "%a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\" \"%{X-Forwarded-For}i\"" real_ip
    CustomLog ${APACHE_LOG_DIR}/yourdomain.com-access.log real_ip
    
    # Standard WordPress configuration
    <Directory /var/www/html>
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

Step 3: Configure WordPress (wp-config.php)

Add this code to your WordPress wp-config.php file before the /* That's all, stop editing! */ line:

<?php
/**
 * Handle SSL termination at reverse proxy
 * This configuration ensures WordPress correctly detects HTTPS
 * and uses real client IP addresses
 */

// Detect HTTPS from reverse proxy
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
    $_SERVER['HTTPS'] = 'on';
    $_SERVER['SERVER_PORT'] = 443;
    
    // Force WordPress admin to use HTTPS
    define('FORCE_SSL_ADMIN', true);
}

// Handle real client IP address for security plugins and logging
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
    $forwardedForHeaders = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
    $_SERVER['REMOTE_ADDR'] = trim($forwardedForHeaders[0]);
}

// Alternative: Use X-Real-IP if available (more direct)
if (isset($_SERVER['HTTP_X_REAL_IP']) && !empty($_SERVER['HTTP_X_REAL_IP'])) {
    $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_REAL_IP'];
}

// Optional: Explicitly set WordPress URLs if database still contains HTTP URLs
// Uncomment and modify if WordPress has persistent URL issues
/*
define('WP_HOME', 'https://yourdomain.com');
define('WP_SITEURL', 'https://yourdomain.com');
*/

// Security: Only trust these headers from known proxy (optional but recommended)
$trusted_proxies = ['10.0.3.1']; // Add your reverse proxy IP
if (!in_array($_SERVER['REMOTE_ADDR'] ?? '', $trusted_proxies)) {
    // Don't process forwarded headers if not from trusted proxy
    // This prevents header spoofing attacks
}
?>

Step 4: Fix WordPress Self-Connection Issues

WordPress makes internal HTTP requests for cron jobs, updates, and health checks. Configure the WordPress server to route these through your reverse proxy.

On your WordPress backend server, edit /etc/hosts:

# Ensure WordPress connects to itself via reverse proxy
10.0.10.1    yourdomain.com    # Replace with your reverse proxy IP

Why this is essential:

  • WordPress Site Health tool requires loopback connectivity
  • Cron jobs and automatic updates need proper SSL handling
  • Plugin API calls must follow the same SSL-terminated path

Step 5: Testing and Verification

Test 1: Check HTTPS Detection

Create a temporary PHP file to verify configuration:

<?php
// test-ssl.php - Remove after testing
echo "<h3>SSL Detection Test</h3>";
echo "HTTPS: " . ($_SERVER['HTTPS'] ?? 'not set') . "<br>";
echo "SERVER_PORT: " . ($_SERVER['SERVER_PORT'] ?? 'not set') . "<br>";
echo "is_ssl(): " . (function_exists('is_ssl') && is_ssl() ? 'true' : 'false') . "<br>";
echo "X-Forwarded-Proto: " . ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? 'not set') . "<br>";
echo "Real IP: " . $_SERVER['REMOTE_ADDR'] . "<br>";
?>

Expected results:

  • HTTPS: on
  • SERVER_PORT: 443
  • is_ssl(): true
  • X-Forwarded-Proto: https
  • Real IP: (actual client IP, not proxy IP)

Test 2: WordPress Site Health

  1. Go to WordPress Admin → Tools → Site Health
  2. Info tab → Server section → “Loopback request” should show ✓
  3. No SSL-related errors should appear

Test 3: Log File Verification

Check your web server logs to confirm real client IPs are being logged instead of the reverse proxy IP.

Test 4: Security Plugin Compatibility

If using security plugins (Wordfence, Sucuri, etc.), verify they detect real client IPs for rate limiting and blocking.

Common Issues and Solutions

Issue: WordPress Admin Redirect Loops

Solution: Ensure FORCE_SSL_ADMIN is defined and X-Forwarded-Proto detection is working

Issue: Mixed Content Warnings

Solution: Check database for hardcoded HTTP URLs, use search-replace tools if needed

Issue: Real IPs Not Logged

Solution: Verify web server real IP configuration and trusted proxy settings

Issue: Plugin API Failures

Solution: Confirm /etc/hosts entry routes domain to reverse proxy, not localhost

Issue: File Upload Problems

Solution: Check client_max_body_size in reverse proxy and backend server limits

Security Considerations

  1. Header Validation: Only process X-Forwarded headers from trusted proxy IPs
  2. Firewall Rules: Ensure WordPress backend is only accessible via reverse proxy
  3. Log Monitoring: Monitor logs for real client IPs to detect abuse patterns
  4. SSL Enforcement: Use HSTS headers at reverse proxy level for additional security

This configuration ensures WordPress operates correctly behind SSL-terminating reverse proxies while maintaining security and proper logging of real client IP addresses.

Similar Posts