Wordpress Nginx redirect loop

Posted on Tue 12 September 2017 in Linux

In short I have Haproxy to terminate SSL, Varnish as cache and finally Nginx to serve fresh content from Wordpress. When using HTTPS Wordpress ended up in a redirect loop to the https:// address of my domain name.

First fix

In time googling brought me to the first fix that actually worked, editing functions.php for my current Wordpress theme and adding the below line at the start of the file:

remove_filter('template_redirect', 'redirect_canonical');

Digging deeper

Editing the source code did not strike me as a very elegant solution and now that I knew where to look it was time to head down the rabbit hole.

Exhibit A, redirect_canonical

Excerpt from the redirect_canonical function in wp-includes/canonical.php:

if ( ! $requested_url && isset( $_SERVER['HTTP_HOST'] ) ) {
    // build the URL in the address bar
    $requested_url  = is_ssl() ? 'https://' : 'http://';
    $requested_url .= $_SERVER['HTTP_HOST'];
    $requested_url .= $_SERVER['REQUEST_URI'];
}

Now this snippet constructs the URL Wordpress thinks the client has requested, is_ssl() sounds really interesting!

Exhibit B, is_ssl

The is_ssl function in wp-includes/load.php:

function is_ssl() {
        if ( isset( $_SERVER['HTTPS'] ) ) {
                if ( 'on' == strtolower( $_SERVER['HTTPS'] ) ) {
                        return true;
                }

                if ( '1' == $_SERVER['HTTPS'] ) {
                        return true;
                }
        } elseif ( isset($_SERVER['SERVER_PORT'] ) && ( '443' == $_SERVER['SERVER_PORT'] ) ) {
                return true;
        }
        return false;
}

So if $_SERVER['HTTPS'] is set and the value is on or 1 we return true, ie "yes we are using https". If $_SERVER['HTTPS'] is not set it also returns true if the traffic is on port 443.

Otherwise no, we are using http.

The issue

SSL (HTTPS) is handled by Haproxy. Nginx (Wordpress) is served with plain HTTP to enable caching by Varnish.

We need the links on the Wordpress page to include to right protocol (https), so we need to set https in Wordpress options, site_url and home.

The problem arises when the Wordpress is_ssl function constructs the URL requested by the visitor via Varnish and correctly determines that Varnish did not ask for an SSL connection. Thus is_ssl constructs the requested url as http://yourdomain.com/.

But in Wordpress you have specified https://yourdomain.com/ as the site_url and home, so Wordpress issues a redirect to that URL and the process starts over, loop!

Solution

Now that we know what is causing the problem and the logic that governs it we can come up with a solution that does not involve editing Wordpress source files.

Override HTTPS var value

This my preferred solution at the moment, use the Nginx parameter fastcgi_param to override the value of $_SERVER['HTTPS']. Edit the PHP location block in your Nginx configuration to something like the below:

location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    fastcgi_param HTTPS 1;
    fastcgi_pass unix:/run/php/php7.0-fpm.sock;
}

Very important to place fastcgi_param after the include statement or it will be overridden by the default value.

Bonus solution: plugin

There is also a Wordpress plugin called SSL Insecure Content Fixer with functionality to for instance "detect" SSL from the X-Forwarded-Proto HTTP header.