dotvitals

A response header leaks an internal address

MediumHigh confidenceQuick winhttp.headers.internal-address-disclosed

What this check looks for

One of your headers contains an address from inside your own network — a private IP or an internal-only hostname. Every visitor receives it, including anyone mapping your infrastructure.

Why it matters

It hands an attacker a free map: which internal ranges you use, how many backends sit behind the load balancer, what your naming convention is. That is the reconnaissance step for server-side request forgery and for lateral movement after any foothold.

When the check passes, your report says: “Response headers keep internal addresses to themselves”.

What it costs your score

When this check fails it removes 8 points from your HTTP score, before the status, confidence and repeat multipliers are applied. HTTP carries a weight of 7 in the overall score.

It shares the http.hygiene family ceiling of 10 points: however many findings that family produces, together they cannot remove more than that from HTTP. One underlying problem showing up in several places is still one problem.

Severity
medium
Default confidence
high
Status when triggered
warn
Deduction
8 points
Family cap
http.hygiene · 10
Category
HTTP
Module
Http headers
Fix owned by
user
In the ruleset since
2026.09

How the whole score is calculated

How to fix it

Stop sending headers that contain internal addresses or hostnames to the public.

They map your internal network for anyone who reads a response, and an internal Location is a redirect no visitor can follow.

  1. Remove the diagnostic headers at the outermost proxy — the last hop before the visitor — so nothing behind it can reintroduce them.

  2. Remove them at the origin too. A proxy that is bypassed, by IP or through an unproxied DNS record, exposes them again.

  3. For an internal Location, fix the cause rather than the symptom: tell the application its public origin (X-Forwarded-Host / X-Forwarded-Proto honoured correctly, or an explicit base-URL setting) so it builds absolute URLs on the public hostname — or emit a relative Location, which RFC 9110 permits.

  4. nginx: proxy_hide_header removes a header arriving from an upstream; removing an arbitrary header needs the headers-more module. Apache: Header always unset.

  5. If you need the header for debugging, make it conditional on a trusted source address rather than sending it to every visitor.

How to confirm it worked

  • curl -sSI https://‹host›/ | grep -inE '(10|127|169\.254|192\.168)\.[0-9]+\.|172\.(1[6-9]|2[0-9]|3[01])\.|100\.(6[4-9]|[7-9][0-9]|1[01][0-9]|12[0-7])\.|\.(local|internal|lan|corp)\b' — expect nothing

  • curl -sSI https://‹host›/ | grep -i '^location:' — any redirect must point at a public hostname

A named slot like ‹domain› — and the braces left in the configuration below — is filled in with your own values when this rule appears on a report.

Remediation by platform

nginx
# Requires the headers-more module (ngx_headers_more):
more_clear_headers 'X-Backend-Server' 'X-Served-By' 'X-Real-IP' 'Via';

# For a header added by a proxied upstream, stock nginx can do this instead:
# proxy_hide_header X-Backend-Server;
Apache
<IfModule mod_headers.c>
	Header always unset X-Backend-Server
	Header always unset X-Served-By
	Header always unset X-Real-IP
	Header always unset Via
</IfModule>
Caddy
header {
	-X-Backend-Server
	-X-Served-By
	-X-Real-IP
	-Via
}
Cloudflare
Rules → Overview → Create rule → Response Header Transform Rule → Remove (path as at 2026-09).
  • Stock nginx cannot remove an arbitrary response header: add_header only adds, and proxy_hide_header works only for a header arriving from a proxied upstream. If neither applies, stop the application emitting it instead — which is the better fix in any case.

  • Shown because the response identified Apache. Put it in the virtual host or server configuration rather than .htaccess: .htaccess is re-read on every request, is often disabled by AllowOverride None, and runs too late to influence a response the virtual host generates itself. always matters here: without it the rule skips error responses, which are frequently the ones that leak the most.

  • This applies only to responses that reach visitors through Cloudflare. An origin that is reachable directly — by IP, or through a DNS record that is not proxied — still serves the response measured here unchanged. Setting it at the origin as well covers both paths.

Technical detail

Headers carrying an internal address on the response from ‹origin›: ‹headers›. ‹detail›

What counts as internal here: RFC 1918 private space (10/8, 172.16/12, 192.168/16), loopback (127/8, ::1), link-local (169.254/16, fe80::/10), IPv6 unique-local (fc00::/7), CGNAT space (100.64/10, RFC 6598), and hostnames under .local, .internal, .lan and .corp.

The headers this usually appears in are Via, X-Served-By, X-Backend-Server, X-Cache-Server, X-Real-IP, Server and — the one that actually breaks things — Location or Content-Location. A Location pointing at an internal address is not merely a disclosure: the redirect is unusable from outside, so the response is broken for every visitor as well as informative to an attacker. It is the signature of a reverse proxy whose upstream does not know its public hostname.

Most of these are diagnostic headers switched on during an incident and never switched off. Where one is genuinely wanted for debugging, gate it behind a request from a trusted source rather than sending it to everyone. Detected server: ‹detected server›.

Standards and references

Test this on your domain

Run the check that produces this finding, on its own, against any domain.

Open the http headers checkerBuild the fix

Other http headers checks