jackl
By:
jackl

Redirect loop with Wordpress on Apache with nginx reverse proxy and HTTPS on Ubuntu 16

May 4, 2017 2.2k views
Apache Nginx WordPress Ubuntu 16.04

I'm experimenting with a DO droplet to host my Wordpress blog, setting it up myself because I'm difficult that way.

I configured Apache with TLS via Lets Encrypt and migrated my Wordpress blog over, and everything was working fine.

I then put nginx in front of Apache as a reverse proxy and configured nginx as the TLS terminator, redirecting any non-https to https using the tutorials here.

Now I can access any static files which are served up by nginx, and accessing a phpinfo() test file on Apache works fine. But accessing any Wordpress php files, like index.php or admin triggers a redirect loop.

What did I do wrong? Here's my nginx config

# Default server configuration
#
server {
        listen 80;
        listen [::]:80;
        server_name demo.EXAMPLE.com;

        # redirect all http to https
        return 301 https://$server_name$request_uri;
}

server {
        # SSL configuration
        listen 443 ssl http2 default_server;
        listen [::]:443 ssl http2 default_server;
        include snippets/ssl-demo.EXAMPLE.com.conf;
        include snippets/ssl-params.conf;

        root /var/www/demo.EXAMPLE.com/public_html;

        index index.php index.html index.htm;

        server_name demo.EXAMPLE.com;

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

        # proxy PHP requests to Apache
        location ~ \.php$ {
                proxy_set_header X-Real-IP  $remote_addr;
                proxy_set_header X-Forwarded-For $remote_addr;
                proxy_set_header Host $host;
                proxy_pass http://127.0.0.1:8080$request_uri;
        }

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one

        location ~ /\.ht {
                deny all;
        }

}

2 Answers
jtittle1 May 4, 2017
Accepted Answer

@jackl

The only issue with your server block that I can see is within the location block that handles PHP.

        location ~ \.php$ {
                proxy_set_header X-Real-IP  $remote_addr;
                proxy_set_header X-Forwarded-For $remote_addr;
                proxy_set_header Host $host;
                proxy_pass http://127.0.0.1:8080$request_uri;
        }

Specifically, this line:

proxy_pass http://127.0.0.1:8080$request_uri;

You shouldn't need to add $request_uri to the end of the proxy, so my first recommendation would be to remove $request_uri from the URL and leave it as:

proxy_pass http://127.0.0.1:8080;

Another issue is most likely due to termination of SSL. WordPress doesn't handle proxies all that well and from what I've read, have no intention on implementing anything to make it easier, so you may need to add a bit of code to your wp-config.php file.

Open wp-config.php and find:

define('WP_DEBUG', false);

Directly below it, add:

if ( $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' )
{
    $_SERVER['HTTPS']       = 'on';
    $_SERVER['SERVER_PORT'] = 443;
}

Close and save the file, then refresh the page and see if you're still seeing the loop.

If yes, and you happen to be using CloudFlare, there's a fix for that too :-). CloudFlare needs to be set to use Full (Strict) under the SSL/Crypto menu (should be the first option). Without Full (Strict), you end up with a loop that seems hopeless.

If none of that works, my first recommendation would be to check the error logs.

tail -50 /var/log/nginx/error.log

Please paste the output in to a code block as a reply.

...

My second recommendation, ditch Apache as you don't need it, even for WordPress (or any other CMS for that matter). NGINX + PHP-FPM can handle more than Apache can with mod_php and the setup is pretty darn simple (I'll be more than happy to help if you want to go that route).

  • Thanks for the help, jtittle.

    Main reason I'm trying Apache+nginx is I'd read pure nginx+fpm turns all your wordpress permalinks into something like "domain.com/index.php?permalinklocation" and besides being ugly I was concerned about problems caching since the URL would have a query string in it.

    I'm not using Cloudflare at the moment, wanted to get this running smoothly with https before I got complicated. Also, still undecided if I want to trade CDN for proxying my secure connections... Was hoping leverage nginx speed plus its cache on a VPS to be fast enough to reduce the need for the CDN.

    Of course, I was also expecting Apache+nginx to be simpler than pure nginx+fpm but didn't anticipate this redirect loop either...

    I tried with and without the $request_uriin proxy_pass, no difference. Same with modifying the wp-config.php. Restarted apache and ngninx after each change.

    No errors in the nginx error.log and apache error.log just shows the service stopping and starting. Access logs just show the redirect loop.

    Do I need to modify my nginx proxy headers to include the request method for the wp-config.php conditional you suggested to work? I'm doing http between Apache and nginx, so I expect Wordpress sees the traffic as http from nginx.

    edited by kamaln7
    • @jackl

      Actually, NGINX + PHP-FPM can be as simple as:

      server
      {
          listen 80;
          listen [::]:80;
          server_name domain.com www.domain.com;
      
          root /home/nginx/htdocs/public;
      
          location /
          {
              try_files $uri $uri/ /index.php?$args;
          }
      
          location ~ [^/]\.php(/|$) {
              fastcgi_split_path_info ^(.+?\.php)(/.*)$;
              fastcgi_pass unix:/run/php/php7.1-fpm.sock;
              fastcgi_index index.php;
      
              include /etc/nginx/config/php/fastcgi_params;
          }
      }
      

      The above works for WordPress and is the NGINX + PHP-FPM solution. The fastcgi_params file contains:

      fastcgi_connect_timeout 60;
      fastcgi_send_timeout 180;
      fastcgi_read_timeout 180;
      fastcgi_buffer_size 512k;
      fastcgi_buffers 512 16k;
      fastcgi_busy_buffers_size 1m;
      fastcgi_temp_file_write_size 4m;
      fastcgi_max_temp_file_size 4m;
      fastcgi_intercept_errors off;
      
      fastcgi_param SCRIPT_FILENAME   $request_filename;
      fastcgi_param PATH_INFO         $fastcgi_path_info;
      fastcgi_param PATH_TRANSLATED   $document_root$fastcgi_path_info;
      fastcgi_param QUERY_STRING      $query_string;
      fastcgi_param REQUEST_METHOD    $request_method;
      fastcgi_param CONTENT_TYPE      $content_type;
      fastcgi_param CONTENT_LENGTH    $content_length;
      fastcgi_param SCRIPT_NAME       $fastcgi_script_name;
      fastcgi_param REQUEST_URI       $request_uri;
      fastcgi_param DOCUMENT_URI      $document_uri;
      fastcgi_param DOCUMENT_ROOT     $document_root;
      fastcgi_param SERVER_PROTOCOL   $server_protocol;
      fastcgi_param REQUEST_SCHEME    $scheme;
      fastcgi_param HTTPS             $https if_not_empty;
      fastcgi_param HTTP_PROXY        "";
      fastcgi_param GATEWAY_INTERFACE CGI/1.1;
      fastcgi_param SERVER_SOFTWARE   nginx/$nginx_version;
      fastcgi_param REMOTE_ADDR       $remote_addr;
      fastcgi_param REMOTE_PORT       $remote_port;
      fastcgi_param SERVER_ADDR       $server_addr;
      fastcgi_param SERVER_PORT       $server_port;
      fastcgi_param SERVER_NAME       $server_name;
      fastcgi_param REDIRECT_STATUS   200;
      

      The only changes you'd need to make are file and directory permissions. By default, PHP will run as www-data, so all files and directories need to be owned by the user, otherwise PHP won't be able to write (which would prevent you from uploading themes, creating cached files, etc).

      For example, I'm using /home/nginx/htdocs/public as the home directory in the above server block, so I'd run:

      chown -R www-data:www-data /home/nginx/*
      

      That fixes all the user:group permissions on all files and directories in one command.

      ...

      As for using the proxy, I normally use a slightly more detailed configuration, which includes the header defined in the code. My proxy configuration normally looks like:

          proxy_buffers 16 32k;
          proxy_buffer_size 64k;
          proxy_busy_buffers_size 128k;
          proxy_cache_bypass $http_pragma $http_authorization;
          proxy_connect_timeout 59s;
          proxy_hide_header X-Powered-By;
          proxy_http_version 1.1;
          proxy_ignore_headers Cache-Control Expires;
          proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504 http_404;
          proxy_no_cache $http_pragma $http_authorization;
          proxy_pass_header Set-Cookie;
          proxy_read_timeout 600;
          proxy_redirect off;
          proxy_send_timeout 600;
          proxy_temp_file_write_size 64k;
          proxy_set_header Accept-Encoding '';
          proxy_set_header Cookie $http_cookie;
          proxy_set_header Host $host;
          proxy_set_header Proxy '';
          proxy_set_header Referer $http_referer;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header X-Forwarded-Host $host;
          proxy_set_header X-Forwarded-Server $host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-Proto $scheme;
          proxy_set_header X-Original-Request $request_uri;
      

      In your case, to use the above, you'd keep proxy_pass http://127.0.0.1:8080; and then replace the lines you have with the above, and then restart NGINX.

      • Thanks again, jtittle!

        That may look simple to you, but to an nginx novice like me, it's complicated :-)

        I was just about to give up, but did manage to get it working. I'm not sure what exactly was the magic that made it work, but it was a combination of modifying the wp-config.php and the requesturi variable.

        Here's what ultimately broke the redirect loop and got Wordpress talking with Apache and nginx.

        nginx config

                location ~ \.php$ {
                        proxy_set_header X-Real-IP $remote_addr;
                        proxy_set_header X-Forwarded-For $remote_addr;
                        proxy_set_header X-Forwarded-Host $host;
                        proxy_set_header Host $host;
                        proxy_set_header X-Forwarded-Proto $scheme;
                        proxy_pass http://127.0.0.1:8080$request_uri;
                }
        
        

        wp-config.php

        if ( $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' )
        {
                $_SERVER['HTTPS']       = 'on';
                $_SERVER['SERVER_PORT'] = '443';
                define('FORCE_SSL_ADMIN', true);
        }
        
        if ( isset($_SERVER['HTTP_X_FORWARDED_HOST'])) {
                $_SERVER['HTTP_HOST'] = $_SERVER['HTTP_X_FORWARDED_HOST'];
        }
        
        

        I suspect I can remove some of the items in wp-config, like FORCESSLADMIN but it works for now, so on to tweaking caching.

        Frankly, I'm a bit disappointed in the performance so far. Granted there is some room for tuning my nginx and apache config but I was expecting much faster page load times than I'm seeing.

        • @jackl

          That may look simple to you, but to an nginx novice like me, it's complicated :-)

          That's why the community exists :-). I'm pretty active here and there are others that drop in as well -- we all try to help as much as possible.

          The example I provided for NGINX + PHP-FPM is very basic and barebones, but it's what's needed for WordPress. Try not to focus so much on how complex it may be at first glance. There's a lot that I refer to the docs on, even as a sysadmin. It's not all that easy to remember every single variable across multiple servers and various languages. Trying to will just give you a migraine :-).

          Take it a step at a time -- in doing so, what may not make sense now will begin to.

          ...

          In regards to performance, you're still offloading dynamic requests to Apache and mod_php, which is resource hungry and relatively slow unless you spend a bit of time tweaking/tuning. Even then, it can still be slower.

          When it comes to tweaking/tuning, however, there's more at play than just the web server. You also have to factor in MySQL/MariaDB for the database as well as what you're using on the backend for WordPress.

          More plugins normally equates to more resources being used.

          You also have to consider that more HTTP requests means slower page load. Even with NGINX serving static content, you still have to make sure static is actually static. That means checking for query strings on files.

          More often than not, WordPress appends version numbers to files, like so:

          ./jquery.js?version=1.0.1
          

          The above is just an example, but it's an example that isn't cacheable. You'd need to remove that query string for jquery.js to be fully cacheable and subject to the expiration times that may be set.

          i.e

          ./jquery.js
          

          There's a lot that goes in to performance tuning and it's two-sided :-).

Have another answer? Share your knowledge.