Canonical URLs, HTTPS, and Drupal 8/9 Reverse Proxy Settings

Submitted on Feb 17, 2021, 11:05 p.m.

Here's a PSA for anyone trying to configure Drupal 8/9 behind a reverse proxy in order to create canonical URLs (in head, and header) that preserve the original remote request protocol - HTTPS.

Our sites are deployed via Docker to AWS ECS - with each service registered as a target for an AWS application load balancer (ALB). The ALB is our reverse proxy.  The ALB correctly creates X-Forwarded-ForX-Forwarded-Proto, and X-Forwarded-Port HTTP headers, and so all that's left is to ensure that your web server, and ultimately PHP are configured to receive these headers, and respond accordingly.

TL;DR - assuming your webserver is configured correctly, below are the settings you'll need to add to settings.php for your site:

$settings['reverse_proxy'] = TRUE;
$settings['reverse_proxy_addresses'] = array($_SERVER['REMOTE_ADDR']);
$settings['reverse_proxy_trusted_headers'] = \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_FOR | \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_PROTO | \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_PORT;

Since the IP address of AWS's ALB can (and does) change, there's no way to set a fixed IP address for the proxy address. There's is an old issue here that would change this setting in Drupal to allow a range, or CIDR address - which would be ideal, but until then you'll need to use the setting above.

You can test whether PHP (and Drupal) are receiving the required headers by placing a file like proxy-test.php in the root of your Drupal website...

<?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";
?>

If you see values for X-Forwarded-For, and X-Forwarded-Proto - you the protocol portion of your canonical URLs should now be correct. Thanks to Alvin Alexander for this tip.

In our case, Nginx is configured by default to forward all headers to fastcgi (PHP-FPM) and so we were receiving the headers fine, however, looking at the HTTP response, both header values and link sources on the page where showing HTTP protocol values for canonical links (not HTTPS) until we enabled the reverse_proxy settings above.

The next problem you'll face, is ensuring that the canonical URL for the home page of your site is https://www.yourdomain.com/ and not www.yourdomain.com/node/7 . If like us, you've set a node (likely a landing page) as the front page of your site (under Configration -> System -> Basic site settings), then you'll need to be aware of this issue - https://www.drupal.org/project/drupal/issues/1255092 . At the moment the best solution is in comment #14 - which offers a service implementing  OutboundPathProcessorInterface to return '/'  for the front page canonical URL.

With all of the above, you should be good to go.

Here are a few more links and discussion on the topic...

https://www.drupal.org/node/3030558
https://www.drupal.org/project/metatag/issues/2842049
https://www.drupal.org/node/425990
https://drupal.stackexchange.com/questions/199722/how-to-correctly-configure-settings-php-to-use-reverse-proxy-and-ssl-termination
https://medium.com/@lmakarov/drupal-8-and-reverse-proxies-the-base-url-drama-c5553cbc9a3e

Hope this helps.