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.

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-ForForwardedTrue-Client-IP, commonly used in Akamai setupsAH_CLIENT_IPorHTTP_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.

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:
- Configure the edge/origin layer to translate the trusted client IP into
X-Forwarded-Forbefore Drupal handles the request. - Use platform-supported Acquia configuration or a carefully reviewed early bootstrap customization that only copies
AH_CLIENT_IPinto 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, orAH_CLIENT_IPvalues. - 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.

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, orX-Forwarded-Foris the expected source of truth. - Do not hard-code a huge public CDN range into
settings.phpunless 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:
| Layer | Responsibility |
|---|---|
| Akamai | Overwrite the client IP header and protect direct origin access. |
| Acquia routing | Preserve or normalize the trusted client IP for the Drupal origin. |
| Drupal settings.php | Trust only the expected proxy addresses and expected forwarded headers. |
| Custom Drupal code | Use \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_ADDRHTTP_X_FORWARDED_FORHTTP_FORWARDEDHTTP_TRUE_CLIENT_IPHTTP_AH_CLIENT_IPHTTP_X_FORWARDED_PROTO

Run these checks:
- Visit the site through Akamai and confirm Drupal reports the real visitor IP.
- Hit the origin directly, if you can. Direct access should be blocked or should not trust spoofed forwarded headers.
- Send a fake
X-Forwarded-Forheader and confirm Drupal does not accept it from an untrusted sender. - Test HTTPS redirects and absolute URL generation after enabling trusted proto/host headers.
- 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_proxyenabled only in environments that actually sit behind a proxy. reverse_proxy_addressesincludes the immediate trusted proxy addresses or provider-recommended ranges.reverse_proxy_trusted_headersincludes 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
- Drupal 11 default.settings.php reverse proxy configuration
- Symfony trusted proxies and trusted headers documentation
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.