How Drupal Handles the Real Client IP Behind Akamai or a CDN

Drupal

How Drupal Handles the Real Client IP Behind Akamai or a CDN

Learn how Drupal determines the real client IP behind Akamai, a CDN, reverse proxies, or Acquia hosting, including AH_CLIENT_IP, trusted headers, and spoofing risks.

When Drupal runs behind Akamai, another CDN, Varnish, a cloud load balancer, or Acquia routing, the IP address Drupal sees by default is usually not the visitor's real IP. It is the IP address of the last reverse proxy that connected to PHP.

That sounds small, but it affects security logs, rate limiting, abuse detection, geo decisions, personalization, analytics, and any custom code that calls \Drupal::request()->getClientIp(). The fix is not to blindly read whatever header looks useful. The fix is to create a trusted chain from the edge to Drupal.

Request flow from visitor through Akamai CDN and reverse proxy into Drupal
Drupal only knows the real client IP when the proxy chain is trusted and the right headers are configured.

The Problem In One Request

Imagine this path:

Visitor 203.0.113.25
  -> Akamai edge
  -> Acquia platform routing or load balancer
  -> Drupal web container

At the PHP layer, $_SERVER['REMOTE_ADDR'] is normally the address of the proxy that connected to Drupal, not the visitor. To preserve the visitor address, proxies commonly add headers such as:

  • X-Forwarded-For
  • Forwarded
  • True-Client-IP, commonly used in Akamai setups
  • AH_CLIENT_IP or HTTP_AH_CLIENT_IP, often seen in Acquia/Akamai-style platform integrations

The important part: these are HTTP headers. A visitor can send fake HTTP headers unless the edge or load balancer strips and overwrites them before traffic reaches Drupal.

How Drupal Decides The Client IP

Drupal uses Symfony's Request object. In ordinary hosting, Drupal treats REMOTE_ADDR as the client address. When reverse proxy support is enabled, Drupal can read trusted forwarding headers. Drupal core's default settings file is explicit about the security model: reverse proxy headers are spoofable, so Drupal must know which proxy addresses are trusted before using them.

The normal Drupal settings are:

  • $settings['reverse_proxy']: enables reverse proxy handling.
  • $settings['reverse_proxy_addresses']: lists the proxy IPs or CIDR ranges Drupal is allowed to trust.
  • $settings['reverse_proxy_trusted_headers']: controls which forwarded headers Drupal trusts.
Example Drupal settings.php reverse proxy trusted headers configuration
Trust proxy addresses and headers together. One without the other is incomplete.

A Safe settings.php Pattern

A simple Drupal example looks like this:

use Symfony\Component\HttpFoundation\Request;

$settings['reverse_proxy'] = TRUE;

$settings['reverse_proxy_addresses'] = [
  '10.0.0.0/8',
  '192.0.2.10',
];

$settings['reverse_proxy_trusted_headers'] =
  Request::HEADER_X_FORWARDED_FOR |
  Request::HEADER_X_FORWARDED_PROTO;

Only trust the headers your proxy really sets. If your architecture does not require Drupal to trust X-Forwarded-Host, do not include it. Host and protocol headers influence absolute URL generation, redirects, and security-sensitive behavior.

Where AH_CLIENT_IP Fits

AH_CLIENT_IP is best understood as a platform or edge-provided client IP signal, not as a Drupal-native trusted header constant. In many Acquia and Akamai-style architectures, the edge knows the visitor IP and forwards it toward the origin using a dedicated header or server variable. Depending on the exact stack, PHP might expose that as $_SERVER['HTTP_AH_CLIENT_IP'], $_SERVER['HTTP_TRUE_CLIENT_IP'], or another normalized value.

Drupal's built-in trusted header configuration is designed around Symfony-supported headers such as X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-Port, and Forwarded. If your platform gives you AH_CLIENT_IP, you generally have two safe options:

  1. Configure the edge/origin layer to translate the trusted client IP into X-Forwarded-For before Drupal handles the request.
  2. Use platform-supported Acquia configuration or a carefully reviewed early bootstrap customization that only copies AH_CLIENT_IP into a Drupal-trusted header after confirming the request came through the trusted platform path.

Do not add custom code that always trusts $_SERVER['HTTP_AH_CLIENT_IP']. That turns a convenience header into an IP spoofing vulnerability.

Akamai Configuration Principles

On Akamai, the key job is to make the edge authoritative. Work with the Akamai property configuration so the CDN:

  • Receives the real visitor IP at the edge.
  • Overwrites the origin-facing client IP header.
  • Does not preserve visitor-supplied X-Forwarded-For, True-Client-IP, or AH_CLIENT_IP values.
  • Sends traffic only to the intended origin.
  • Uses HTTPS between edge and origin where possible.

If Akamai forwards True-Client-IP but your Drupal code expects X-Forwarded-For, decide where normalization belongs. Usually the cleanest place is the edge, load balancer, or platform routing layer, not arbitrary business logic inside Drupal.

Reverse Proxies And Header Chains

X-Forwarded-For can contain a chain of addresses:

X-Forwarded-For: 203.0.113.25, 198.51.100.20, 10.0.4.12

The leftmost address is usually the original visitor, and each proxy appends itself. But Drupal should only evaluate that chain when the immediate sender is trusted. If the immediate sender is not trusted, the whole chain is just user input.

This matters in multi-proxy setups. If traffic flows from Akamai to an Acquia balancer to Drupal, Drupal may only directly see the Acquia balancer. The trusted proxy list must match the actual hops that connect to Drupal. When those hops use private ranges or managed platform ranges, follow the hosting provider's recommended pattern instead of guessing.

Spoofing Risk: The Mistake To Avoid

The dangerous pattern looks like this:

// Do not do this globally.
if (!empty($_SERVER['HTTP_AH_CLIENT_IP'])) {
  $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_AH_CLIENT_IP'];
}

That code trusts an HTTP header before proving the request came from a trusted proxy. If your origin is reachable directly, an attacker can send:

AH_CLIENT_IP: 127.0.0.1
X-Forwarded-For: 127.0.0.1

Then IP-based allowlists, fraud checks, rate limits, or admin protections may make decisions from a forged value.

Spoofing risk when Drupal trusts client-supplied forwarded IP headers
A forwarded IP header is trustworthy only if a trusted proxy overwrites it and direct origin access is blocked.

Acquia Hosting Considerations

On Acquia-hosted Drupal sites, assume there are managed routing layers in front of your application. That changes the operational question from “what are Akamai's IP ranges?” to “what does Acquia recommend for this application and this subscription?”

Practical guidance:

  • Check whether Acquia already normalizes the client IP for Drupal in your environment.
  • Confirm whether AH_CLIENT_IP, True-Client-IP, or X-Forwarded-For is the expected source of truth.
  • Do not hard-code a huge public CDN range into settings.php unless Acquia has confirmed that is the right pattern for your stack.
  • Prefer Acquia-supported includes, environment variables, or platform documentation over custom one-off header rewriting.
  • Make sure the origin cannot be reached directly outside Akamai or Acquia routing.

If you use both Akamai and Acquia, define ownership clearly:

LayerResponsibility
AkamaiOverwrite the client IP header and protect direct origin access.
Acquia routingPreserve or normalize the trusted client IP for the Drupal origin.
Drupal settings.phpTrust only the expected proxy addresses and expected forwarded headers.
Custom Drupal codeUse \Drupal::request()->getClientIp(), not raw headers.

How To Test The Setup

Start by checking what Drupal thinks the client IP is:

drush php:eval 'echo \Drupal::request()->getClientIp() . PHP_EOL;'

For deeper inspection, temporarily log selected server variables in a safe non-production route or use Drush on a controlled request. Look for:

  • REMOTE_ADDR
  • HTTP_X_FORWARDED_FOR
  • HTTP_FORWARDED
  • HTTP_TRUE_CLIENT_IP
  • HTTP_AH_CLIENT_IP
  • HTTP_X_FORWARDED_PROTO
Debug checklist for Drupal client IP behind a CDN
Test the whole chain: edge behavior, origin reachability, PHP server variables, and Drupal's Request object.

Run these checks:

  1. Visit the site through Akamai and confirm Drupal reports the real visitor IP.
  2. Hit the origin directly, if you can. Direct access should be blocked or should not trust spoofed forwarded headers.
  3. Send a fake X-Forwarded-For header and confirm Drupal does not accept it from an untrusted sender.
  4. Test HTTPS redirects and absolute URL generation after enabling trusted proto/host headers.
  5. Check logs from Akamai, Acquia, the web server, and Drupal for the same request.

Common Problems

Drupal always shows the proxy IP

Reverse proxy handling is not enabled, the proxy address is missing, or the expected forwarded header is not trusted.

Drupal shows a private IP address

The last internal proxy is being treated as the visitor, or the header chain is not being forwarded through all layers.

Users can spoof their IP in tests

The origin is reachable directly, a proxy is appending instead of overwriting, or custom code is reading raw headers without validating the sender.

HTTPS redirects loop after enabling reverse proxy

Drupal may not trust the protocol header, or the proxy may not be setting X-Forwarded-Proto: https. Verify both sides before changing redirect logic.

Different environments behave differently

Local, staging, and production often have different proxy paths. Keep environment-specific proxy addresses in environment-specific settings, not shared config exported through Drupal's config system.

Checklist For Production

  • Origin traffic is restricted to Akamai, Acquia routing, or the intended load balancer path.
  • The edge overwrites client IP headers instead of preserving visitor-supplied values.
  • Drupal has reverse_proxy enabled only in environments that actually sit behind a proxy.
  • reverse_proxy_addresses includes the immediate trusted proxy addresses or provider-recommended ranges.
  • reverse_proxy_trusted_headers includes only the headers your architecture uses.
  • Custom code reads \Drupal::request()->getClientIp() instead of raw HTTP headers.
  • Security controls do not depend on IP alone unless the proxy chain has been tested.
  • Run a spoofing test before launch.

Reference Links

Final Takeaway

The real client IP is not a single header. It is the result of a trusted network path. For Drupal behind Akamai or another CDN, the safest model is: restrict origin access, make the edge overwrite the client IP header, configure Drupal to trust only the correct proxy addresses and headers, and make application code use Drupal's Request object.

If AH_CLIENT_IP is part of your hosting stack, treat it as a signal that must be normalized by a trusted layer. Do not trust it just because it has a useful name.

Keep reading

Drupal Sep 7, 2026 6 min read

Build a Drupal Chatbot with Local AI

Learn how a Drupal chatbot finds relevant published articles, uses Ollama to generate answers, and displays source links while keeping inference on your own hardware.