Getting the Drupal 8 Metatag module to show a canonical HTTPS URL

As a brief note, this website currently uses Drupal 8 and I was having a problem where the Metatag module was showing a canonical tag that began with http rather than https, which this website uses. The rest of this article briefly shows how I was able to fix this problem.

Please note that I don’t explain things in great detail. I’m just putting notes here for myself in case I need to do something like this again. Hopefully they’ll be helpful to other people as well.

Background

This website currently uses Nginx on the front end, and then it passes requests intended for Drupal to Apache 2.x, so Nginx works as a proxy server. The Nginx server has the SSL certificate information and handles HTTPS requests.

From drupal.org

The comments beginning with comment #31 on this drupal.org page point to part of the problem. Several things need to be in place from a Drupal perspective, including that the settings.php file needs to have lines like these:

$settings['reverse_proxy'] = TRUE;
$settings['reverse_proxy_addresses'] = array($_SERVER['REMOTE_ADDR']);

Those settings are helpful to know, but they’re only part of the story. Indeed, I already had my settings.php file configured like that, and the canonical setting was still showing http instead of https.

Note that this page also says that the problem isn’t with the Metatag module, because it gets the URL directly from the Drupal Token module. This implies that Token is seeing an 'http' URL rather than an 'https' request.

Nginx settings

There are a number of Nginx settings which are important because Nginx needs to pass this information on to Apache. Without getting into the details of each setting, I have these settings in the general Nginx http block:

proxy_buffering   off;
proxy_set_header  X-Real-IP $remote_addr;
proxy_set_header  X-Scheme $scheme;
proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header  Host $host;

# protocol setting (should pass 'http' or 'https' to apache)
proxy_set_header  X-Forwarded-Proto $scheme;

I also have these settings in the server block for this website:

fastcgi_param HTTPS on;
fastcgi_param HTTP_SCHEME https;

I added the X-Forwarded-Proto setting and the two fastcgi_param settings today. I haven’t tested them yet to see if they’re all necessary. If I remember right, the fastcgi_param settings came from a Drupal page I read earlier today. The X-Forwarded-Proto is the one that’s supposed to pass the protocol (http or https) information on to Apache.

After you make changes to nginx.conf always be sure to test them with nginx -t and if everything is okay, restart Nginx with service nginx restart.

Apache settings

As it turns out, Apache was a major cause of my problems. I tested Apache by putting this PHP code in a file on my server:

<?php
    header( 'Content-Type: text/plain' );
    echo 'Host: ' . $_SERVER['HTTP_HOST'] . "\n";
    echo 'Remote Address: ' . $_SERVER['REMOTE_ADDR'] . "\n";
    echo 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'] . "\n";
    echo 'X-Forwarded-Proto: ' . $_SERVER['HTTP_X_FORWARDED_PROTO'] . "\n";
    echo 'Server Address: ' . $_SERVER['SERVER_ADDR'] . "\n";
    echo 'Server Port: ' . $_SERVER['SERVER_PORT'] . "\n\n";
?>

(Please note that I found that script on a website, and I can’t remember which one.)

When I hit that PHP script from my browser I saw this output:

Host: alvinalexander.com
Remote Address: 127.0.0.1
X-Forwarded-For: 65.162.144.101
X-Forwarded-Proto: 
Server Address: 127.0.0.1
Server Port: 80

When I saw that the X-Forwarded-Proto field was blank, I realized that Apache was my immediate problem, not Drupal. (This assumes that Nginx is passing the information to Apache, but Apache is not receiving it for some reason.)

In short, I found that I needed to add this setting to the VirtualHost block in my Apache configuration file:

RequestHeader set X-Forwarded-Proto "https"

However, that didn’t work because I didn’t have the Apache mod_headers module installed. So I installed it with this command:

a2enmod headers

and enabled it with this command:

service apache2 restart

Note that you can show which Apache modules are installed/enabled with this command:

apache2ctl -M

Also, when you make updates to an Apache configuration file you should check the configuration with this command before restarting the Apache server:

apachectl configtest

Problem solved

Once I did this, the PHP script I used for testing showed https in the X-Forwarded-Proto field. This told me that Apache was now receiving the http/https “protocol” field information, and when I viewed the source code for one of my web pages on this site, I saw that the canonical field now showed my canonical URL now properly beginning with https — problem solved.

Update

Update: As I wrote this I realized that this “solution” is still ignoring the protocol information that’s being sent from Nginx to Apache. Rather than Apache receiving what Nginx is giving it, this line in the Apache configuration simply sets the protocol to https:

RequestHeader set X-Forwarded-Proto "https"

I tried not using that line in my Apache configuration file, but when I leave it out the canonical URL reverts back to showing http, so for whatever reason, Apache isn’t getting the X-Forwarded-Proto information from Nginx.

Because that information is in the VirtualHost block for this website, it doesn’t do any harm, but the problem is that it hard-codes https rather than using what Nginx is telling it to use. I’m out of time for this project today, but I’ll look into it again when I have more time.

Also, please note that I tried these settings in my Apache configuration file and they did not work:

RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
RequestHeader set "X-Forwarded-SSL" expr=%{HTTPS}

Helpful URLs

These web pages helped me solve this problem: