How to Set up Multisite WordPress Behind an Apache Reverse Proxy with New LetsEncrypt SSL Certs: Changing URLs

This article explains how to fix a redirect infinite loop problem that happens on the wp-admin login. It also explains how to set up WordPress multisite backend and Apache reverse proxy frontend with SSL/TLS certificates from LetsEncrypt.

I’ll get to the config changes below, but the fix for the bug is simple.

WordPress Admin Login Redirects Forever on Server Behind Reverse Proxy with https

The problem is that the communication between the reverse proxy and wp is http, not https. The answer was found here:

https://serverfault.com/questions/955110/wp-admin-redirect-loop-when-behind-apache-reverse-proxy/955471#955471

It’s also described in Administration Over SSL.

The fix is to add to wp-config.php:

$_SERVER['HTTPS'] = 'on';
$_SERVER['SERVER_PORT'] = 443;
if (isset($_SERVER["HTTP_X_FORWARDED_FOR"])) {
    $parts = explode(',', $_SERVER["HTTP_X_FORWARDED_FOR"]);
    $_SERVER['REMOTE_ADDR'] = $parts[0];
}

Maybe there’s a way to detect if HTTPS is being used on the proxy, and then transmit that information back to the application, but this works for now.

Setting the port to 443 fools other code, and was found here.

I added the modification to set the REMOTE_ADDR so comments would be associated with the client’s IP address. This was based on the informal spec at MDN.

Some of my WordPress sites have two IP addresses in HTTP_X_FORWARDED_FOR because the Apache proxy sends the request to a Varnish proxy, which sends it to the application.

Once you do this, you need to make sure the sites prefer https URLs over http, and all the siteurls and home urls are changed over to https.

Multisite Headaches

Multisite configurations have primary site and subsites.

Subsites are easy to reconfigure – you just make change in the admin – but the primary site cannot be changed in the admin. You need to use a mysql client and change some values in the database.

Before we do that, however, you can get certificates from LetsEncrypt.

Using Certbot to Make LetsEncrypt Certs for Apache VirtualHosts with Multisite WordPress

Install LetsEncrypt according to their instructions, but don’t make certs yet.

https://certbot.eff.org/

The best way to set up Apache VirtualHost for Multisite WordPress is to create only one VirtualHost section, and use ServerName and ServerAlias to identify the hosts. Here’s part of my config on the Docker container that serves this site:

<VirtualHost *:80>
    DocumentRoot /opt/docker/apache/www/technote.fyi/public
    ServerName technote.fyi
    ServerAlias riceball.com
</VirtualHost>

My "network" is Docker containers, not a real network.

On the reverse proxy, which is on the host, my config is like this:

<VirtualHost 216.240.146.66:80>
    ServerName technote.fyi
    ServerAlias riceball.com
    ProxyPreserveHost on
    ProxyPass "/" "http://172.16.238.1:9876/"
</VirtualHost>

That config covers only HTTP.

This configuration proxies requests for this domain to the server 172.16.238.1:9876, which was configured above. Over on the 172.16.238.1:9876 server, it determines the VirtualHost to use based on the Host header, which is passed to it because ProxyPreserveHost was on.

So, the setup is simple: a request for http://technote.fyi/ matches the VirtualHost on the proxy, and the request is forwarded to 172.16.238.1:9876, where it matches the VirtualHost, again.

LetsEncrypt Certs Should Be Arranged the Same Way as WP Multisite

LetsEncrypt site certs can apply to more than one domain. So you can create a single cert for all your domains.

However, this can get a little confusing, so I set up one cert per Multisite WP installation.

Each cert verifies the primary and subsite domains.

You create it with this command, as root. (Don’t use my domain!):

certbot --apache -d technote.fyi

You use your own domain, of course. The command will alter your Apache configuration and create a VirtualHost for this domain. Double check its work. Certbot copies your existing configuration, sets the port to 443, and then adds three lines pointing to the cert.

To add domains to the certificate:

certbot --cert-name technote.fyi -d technote.fyi,riceball.com

The VirtualHost on the reverse proxy matches all those domains, and the cert applies to all those domains.

Make Changes in Network Admin

Go back to WordPress’s admin, and go into the Network Admin menu.

Edit all the sites.

For each subsite, fix the URL, so it starts with "https" instead of "http".

You will notice that you cannot change the URL of the primary site.

Makes Changes in the MySQL Database

To change the URL in the primary site, you need to change three rows. One is in wp_sitemeta, and two are in wp_options.

wp_sitemeta

The first row is in the wp_sitemeta table. Look for it with:

mysql> select meta_key,meta_value from wp_sitemeta where meta_key='siteurl';
+----------+-----------------------+
| meta_key | meta_value            |
+----------+-----------------------+
| siteurl  | http://technote.fyi/ |
+----------+-----------------------+
1 row in set (0.01 sec)

To change that value, run this query:

update wp_sitemeta set meta_value='https://technote.fyi/' where meta_key='siteurl';

Use your own URL, of course.

wp_options

There are at least two URLs to change. To find them:

select * from wp_options where option_value like 'http%';

The option names that you must change are site url and home. If you find other URLs, you might want to alter those as well.

Issue commands like this – you are just changing the "http" to "https":

update wp_options set option_value='https://technote.fyi/' where option_name='siteurl';
update wp_options set option_value='https://technote.fyi/' where option_name='home';

If you turned up the wrong URL, it’s possible that the primary site was changed. You will need to search and update in other tables.

The WordPress Multisite table names use this convention %prefix%%blog_id%_tablename. The options table will have names like this:

wp_options
wp_2_options
wp_3_options

You can discover the blog IDs with this query:

select * from wp_blogs;