Using a load balancer or reverse proxy

Last updated on
3 September 2025

This documentation needs review. See "Help improve this page" in the sidebar.

Overview

When running large Drupal installations, you may find yourself with a web server cluster that lives behind some HTTP reverse proxies such as WAF, cache, load balancer... meaning that Drupal receive an altered request. The pages here contain tips for configuring Drupal in this setup, as well as example configurations for various load balancers. Below, the terms reverse proxy and load balancer are mostly interchangeable; the name used emphasises a different aspect of the functionality, but in essence their workings are very similar and for the purpose of this explanation, there is no real difference from Drupal's point of view.

In addition to a large selection of commercial options, various open source load balancers exist: HAProxy, Pound, Varnish, ffproxy, tinyproxy, etc. Web servers (including Apache and NGINX) can also be configured as reverse proxies.

The basic layout you can expect in most high-availability environments will look something like this:

                                       ┌──► Web server 1
Browser ───► HTTP Reverse Proxy (LB) ──┼──► Web server 2
                                       └──► Web server 3

From left to right:

  • Browsers will connect to a reverse proxy using (secure) HTTPS (or, exceedingly less, HTTP).
  • Web servers will likely be on a private network. This means it is safe for the reverse proxy to connect to the web servers over (unsecure) HTTP.
  • If HTTPS is required, it is configured on the proxy, not the web server. The Proxy acts as a "https termination".
  • This is simplified schema, it is common to have a chain of several reverse proxies before web server.

Most HTTP reverse proxies will also "clean" requests in some way. For example, they'll require that a browser includes a valid User-Agent string, or that the requested URL contains standard characters or does not exceed a certain length.

Missing or incorrect configuration could lead to the followings errors:

  • Drupal does not detect the correct protocol; it returns HTTP URLs or links even if the page is called with HTTPS. Cookies are not set to secure mode;
  • Drupal does not detect the correct hostname; it returns URLs with an IP-address, or a local or internal hostname, instead of the called one;
  • Users can not log into Drupal, with it complaining about too may failed login attempts, because it did not correctly detect the origin IP, and the flood module bans the reverse proxy IP, thereby blocking all traffic.

Configuration

The configuration required to enable this environment consists of:

  • Setting the configuration value reverse_proxy to TRUE,
  • Setting the configuration value reverse_proxy_addresses to an array containing the trusted IP address(es) of one or more HTTP Reverse Proxy servers, as seen from the web servers.

The default Drupal settings.php will contain detailed information on the following variables:

  • $settings['reverse_proxy']
  • $settings['reverse_proxy_addresses']
  • $settings['reverse_proxy_trusted_headers']

A more detailed explanation on why these settings are needed can be found in the article Drupal 8 and reverse proxies: The $base_url drama.

If you only need these settings on a production site, you can place these variables in a settings.local.php file. Read the comments and uncomment the section of code at the bottom of your settings.php to set this up.

Example

Here's an example that demonstrates how to tell Drupal sites that they're running behind an HTTPS proxy, which terminates the encryption before getting to the Web server. While the Web server doesn't need to handle HTTPS requests, Drupal sites still need to know that they're being accessed that way.

<?php 
/**
 * Reverse Proxy Configuration:
 *
 * Reverse proxy servers are often used to enhance the performance
 * of heavily visited sites and may also provide other site caching,
 * security, or encryption benefits. In an environment where Drupal
 * is behind a reverse proxy, the real IP address of the client should
 * be determined such that the correct client IP address is available
 * to Drupal's logging, statistics, and access management systems. In
 * the most simple scenario, the proxy server will add an
 * X-Forwarded-For header to the request that contains the client IP
 * address. However, HTTP headers are vulnerable to spoofing, where a
 * malicious client could bypass restrictions by setting the
 * X-Forwarded-For header directly. Therefore, Drupal's proxy
 * configuration requires the IP addresses of all remote proxies to be
 * specified in $settings['reverse_proxy_addresses'] to work correctly.
 *
 * Enable this setting to get Drupal to determine the client IP from the
 * X-Forwarded-For header. If you are unsure about this setting, do not have a
 * reverse proxy, or Drupal operates in a shared hosting environment, this
 * setting should remain commented out.
 *
 * In order for this setting to be used you must specify every possible
 * reverse proxy IP address in $settings['reverse_proxy_addresses'].
 * If a complete list of reverse proxies is not available in your
 * environment (for example, if you use a CDN) you may set the
 * $_SERVER['REMOTE_ADDR'] variable directly in settings.php.
 * Be aware, however, that it is likely that this would allow IP
 * address spoofing unless more advanced precautions are taken.
 */
$settings['reverse_proxy'] = TRUE;

/**
 * Specify every reverse proxy IP address in your environment.
 * This setting is required if $settings['reverse_proxy'] is TRUE.
 */
$settings['reverse_proxy_addresses'] = ['1.2.3.4', ...];

/**
 * Reverse proxy trusted headers.
 *
 * Sets which headers to trust from your reverse proxy.
 *
 * Common values are:
 * - \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_FOR
 * - \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_HOST
 * - \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_PORT
 * - \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_PROTO
 * - \Symfony\Component\HttpFoundation\Request::HEADER_FORWARDED
 *
 * Note the default value of
 * @code
 * \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_FOR | \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_HOST | \Symfony\Component\HttpFoundation\Request::
HEADER_X_FORWARDED_PORT | \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_PROTO | \Symfony\Component\HttpFoundation\Request::HEADER_FORWARDED
 * @endcode
 * is not secure by default. The value should be set to only the specific
 * headers the reverse proxy uses. For example:
 * @code
 * \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_FOR | \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_HOST | \Symfony\Component\HttpFoundation\Request::
HEADER_X_FORWARDED_PORT | \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_PROTO
 * @endcode
 * This would trust the following headers:
 * - X_FORWARDED_FOR
 * - X_FORWARDED_HOST
 * - X_FORWARDED_PROTO
 * - X_FORWARDED_PORT
 *
 * @see \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_FOR
 * @see \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_HOST
 * @see \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_PORT
 * @see \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_PROTO
 * @see \Symfony\Component\HttpFoundation\Request::HEADER_FORWARDED
 * @see \Symfony\Component\HttpFoundation\Request::setTrustedProxies
 */
# $settings['reverse_proxy_trusted_headers'] = \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_FOR | \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_HOST |
\Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_PORT | \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_PROTO | \Symfony\Component\HttpFoundation\Request::HEADER_FORWARDED;

Details

Since Drupal 8, the reverse proxy is done in the Symfony part: https://symfony.com/doc/current/deployment/proxies.html

You could find more information about "forwarded" headers here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For#directives

If you sysadmin could not provide you the list of the trusted proxies IPs, a simple method is to display a phpinfo() page : $_SERVER['REMOTE_ADDR']  contains the closest proxy IP, $_SERVER['HTTP_X_FORWARDED_FOR']  contains he other ones,  do not include the leftmost one which is the real client remote IP.

You could also check if the proxies are forwarding the correct information by checking the following values $_SERVER['HTTP_X_FORWARDED_PROTO'], $_SERVER['HTTP_X_FORWARDED_PORT'] & $_SERVER['HTTP_X_FORWARDED_HOST']

You could check the protocol or host problem by checking the url return on the page Administration >  Configuration > System> Site information  (https://mywebsite.tld/admin/config/system/site-information), or by using this Drupal methods

<?php 
\Drupal::request()->getPort(); 
\Drupal\Core\Url::fromRoute('<front>',[], ['absolute' => 'true'])->toString(); 

Ignore proxies addresses validation

If you trust all the proxy chain and you are sure that nobody could alter the x-forwarded-* headers

You could install the Trusted Reverse Proxy module.
On your local environment  (such as DDEV, lando, ...) you could use this code in your settings.php file:
 
$settings['reverse_proxy'] = TRUE;
# If you got only one local proxy
$settings['reverse_proxy_addresses'] = [$_SERVER['REMOTE_ADDR']];
# If you got an additional local proxy, a varnish container for example:
# $settings['reverse_proxy_addresses'] = [$_SERVER['REMOTE_ADDR'],gethostbyname('varnish')];

Help improve this page

Page status: Needs review

You can: