ERR_TOO_MANY_REDIRECTS by WordPress on LAMP stack with nginx/HTTPS as reverse proxy

I wanted to setup my new WordPress blog on my (new) server running on Ubuntu to have nginx/HTTPS as the reverse proxy for my Apache HTTP Webserver (LAMP stack). Although they are great materials, following the 2 tutorials of DigitalOcean gave me a hard time due to the ERR_TOO_MANY_REDIRECTS caused by WordPress:

Reason for ERR_TOO_MANY_REDIRECTS

Please be aware, that the ERR_TOO_MANY_REDIRECTS can have different (and !multiple!) causes. Although most of the blog articles and forum discussions, that I read during my research of almost 1 week are related to misconfiguration in the wp-config.php, which we will also discuss here.

And yes, it took almost 1 week to figure out the fix for my problem :'(.

nginx keep adding /index.php

After you finish the tutorials from DigitalOcean, you will end up with a nginx configuration file, which has the following lines:

# ...

location / {
    try_files $uri $uri/ /index.php;
}

location ~ \.php$ {
    proxy_pass http://your_server_ip:1986;
    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;
}

# ...

With this configuration, you are most likely fine for most cases. You can access any php files using https. nginx will forward it to Apache, which runs the script. But with WordPress it is not working as you want instead you will end up in ERR_TOO_MANY_REDIRECTS. Well, to be precise, it does run as it is specified:

  1. The ‘https://blog.draphony.com’-HTTPS request reaches your server.
  2. nginx run into the location-block with the try_files. Since this is not pointing to a valid file or valid (sub-)directory but the root itself. nginx will redirect you to the value of the last parameter ‘/index.php’ resulting in ‘https://blog.draphony.com/index.php’. (check try_files for more information).
  3. With the ending ‘.php’, the new URI ‘https://blog.draphony.com/index.php’ makes nginx to use the second location-block, which forward it to the Apache server. 
  4. When Apache use PHP to interpret the WordPress code, WordPress will see, that your URI has an ‘index.php’ at the end. So it will trigger a redirect to ‘https://blog.draphony.com’.
  5. This leads us back to step 1 and ultimately in an endless loop, that would never ends. The ERR_TOO_MANY_REDIRECTS is born.

WordPress keep using http

Assuming nginx is properly configured (meaning the problem described above is solved), the WordPress blog will still not load properly. It will miss all the assets such as CSS-files, JavaScript-files, images and so on.

When adding HTTPS (using Let’s Encrypt or any other solutions) to nginx, the communication between nginx and Apache remains without encryption since Apache is running without SSL configured. So, when nginx forward a request to Apache, a default installation of WordPress thinks that the blog is running without SSL and request all it assets through HTTP instead of HTTPS. Since mixed content are not permitted by most modern browser, the blog is running without its assets.

Eliminate 
ERR_TOO_MANY_REDIRECTS

The solution will consist of multiple parts:

  1. Reconfigure nginx: We need to prevent nginx from adding the ‘index.php’ at the end.
  2. Enforce https: We need to let WordPress, that it should stick to https although it is running on http.
  3. If you have caching plugins installed, you should clear the cache afterwards.

Reconfigure nginx

We need to configure the nginx server. Please open the site configuration of nginx, which should be somewhat like this:

sudo vi /etc/nginx/sites-enabled/{siteconfig}

{siteconfig} is the filename of your site’s configuration file in which we need to replace its content like this:

server {
    server_name example.io www.example.io;
    root /var/www/example.io;
    index index.php index.htm index.html;

    location ~ /\.ht {
        deny all;
    }

    location / {
        proxy_pass http://localhost:1998;

        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;
        proxy_set_header X-Forwarded-Port 443;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/example.io/fullchain.pem;  # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/example.io/privkey.pem;  # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

server
{
    listen 80;
    server_name example.io www.example.io;

    if ($host = www.example.io) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    if ($host = example.io) {
        return 301 https://www.$host$request_uri;
    } # managed by Certbot

    return 404; # managed by Certbot
}

You need to replace the content with your website’s domain and proxy port. I used the 1998.

Enforce https

Enforcing the https in WordPress is quite straight forward. You just need to update your wp-config.php according to the official WordPress recommandation.

So, open your wp-config.php and put the following lines into it. The position doesn’t matter.

<?php
// other stuffs ...

// You can put this everywhere you like. i.e. below define('WP_DEBUG', false);
define('FORCE_SSL_ADMIN', true);

// in some setups HTTP_X_FORWARDED_PROTO might contain
// a comma-separated list e.g. http,https
// so check for https existence
if (strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false)
    $_SERVER['HTTPS']='on';

// other stuffs ...
?>

Setting the $_SERVER[‘HTTPS’] to the string ‘on’ will make any subsequent calls of is_ssl to return true (see WordPress).

When generating the URI for requesting its assets or for redirects, WordPress calls is_ssl(). If it returns true, it will use the string ‘https’ otherwise it will go for ‘http’. It is used quite extensively.

Migrating existing site to https

When you add SSL to an existing site, you may also run into the problem, that in some of your posts, WordPress still trying to access some files in http. In this case, the post itself is containing the complete links. You have to change this (manually). Maybe there are also some tools for this.