Using WordPress With a Reverse Proxy or Load Balancer
I'm deploying some WordPress sites behind a Nginx Proxy Manager, which acts as a reverse proxy with SSL termination. Doing so causes a couple of issues that had to be resolved through some configuration change, which I am documenting them here so I know what to do next time.
Proxy - Set Headers
Your proxy needs to set the X-Forwarded-Proto
and X-Forwarded-For
headers for your application to be able to figure out what's going on later (details in sections below)
Luckily for me, Nginx proxy manager automatically does this for me, but if you need to manually add these to your Nginx configuration, you would need to add the following lines:
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
Prevent Redirect Loop
If you are using SSL termination (e.g. the WordPress server does not listen on port 443, because you are terminating the SSL connection at the proxy), then Wordpress will think that the client is using plain HTTP, and will try to redirect them to an HTTPS connection. Due to the fact that they are already on an HTTPS connection, it is just that WordPress doesn't know this, this will cause an infinite loop, and the user will likely get a "too many redirects" error message.
The solution is to tell WordPress that it is already on an HTTPS connection by editing the wp-config.php
file, and adding the following block:
# Gracefully handle terminated SSL at reverse proxy
if (strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false) {
$_SERVER['HTTPS'] = 'on';
}
Be sure to put this in the section before the following comment:
/* That's all, stop editing! Happy publishing. */
Loopback Requests Issue
When you go to the site health area, you will likely see a message stating that:
The REST API encountered an error
...and :
Your site could not complete a loopback request
This is likely because you may have set the server up so that the /etc/hosts file points requests to its own domain to go to 127.0.0.1. This is usually what one wants to do. Unfortunately, WordPress will send the requests to itself using HTTPS, and the local server is not listening on port 443 for HTTPS connections. It is only listening on port 80 for plain HTTP connections.
Unfortunately, the solution is to edit your /etc/hosts
file, and set it so that your site's domain name is the IP address of your reverse proxy.
You will likely want to use it's internal/private IP (such as 192.168.0.x), rather than the public IP, in order to reduce latency etc.
This way, when a loopback request is made, it will still be able to work with HTTPS on port 443 because it is actually being served by the reverse-proxy instead.
I would love to be able to set it to 127.0.0.1 and to use plain HTTP on port 80, but I have not found a way to do that yet. If you know how to do this, please tell me in the comments!
IP Address Headers
With the default setup, your WordPress site will think all requests are originating from a single IP, your proxy. We need to perform the following change to the wp-config.php file, in order to let WordPress know where the requests are really coming from (where they are being forwarded from).
if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
{
$forwardedForHeaders = explode( ',', $_SERVER['HTTP_X_FORWARDED_FOR'] );
$_SERVER['REMOTE_ADDR'] = $forwardedForHeaders[0];
}
Again, be sure to put this in the section before the following comment:
/* That's all, stop editing! Happy publishing. */
Htaccess Whitelist
It is not uncommon to implement an IP whitelist on your admin/login area. If using a proxy, you probably need to update this to change from:
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{REQUEST_URI} ^(.*)?wp-login\.php(.*)$ [OR]
RewriteCond %{REQUEST_URI} ^(.*)?wp-admin$
RewriteCond %{REMOTE_ADDR} !^xxx.xxx.xxx.xxx$
RewriteCond %{REMOTE_ADDR} !^yyy.yyy.yyy.yyy$
RewriteCond %{REMOTE_ADDR} !^zzz.zzz.zzz.zzz$
RewriteRule ^(.*)$ - [R=403,L]
</IfModule>
...to:
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{REQUEST_URI} ^(.*)?wp-login\.php(.*)$ [OR]
RewriteCond %{REQUEST_URI} ^(.*)?wp-admin$
RewriteCond %{HTTP:X-FORWARDED-FOR} !^xxx.xxx.xxx.xxx$
RewriteCond %{HTTP:X-FORWARDED-FOR} !^yyy.yyy.yyy.yyy$
RewriteCond %{HTTP:X-FORWARDED-FOR} !^zzz.zzz.zzz.zzz$
RewriteRule ^(.*)$ - [R=403,L]
</IfModule>
REMOTE_ADDR
changed to HTTP:X-FORWARDED-FOR
.
References
First published: 16th June 2023