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.