How would you expose Apache httpd's mod_status page safely, and why can an IP-based restriction on /server-status fail to protect it when Apache sits behind a reverse proxy or CDN?
answer
- SetHandler, not a file on disk
- the page leaks live request lines
- Require ip tests the peer, not the client
- behind a proxy every client looks the same
- an unreachable endpoint beats a trusted rule
basics
~20 sScope it with a <Location> block using SetHandler server-status and an explicit Require rule. Behind a proxy, the connection's peer is the proxy, so Require ip matches for every visitor and the restriction fails open unless mod_remoteip restores the real client address.
solid answer
~50 smod_status is enabled by putting `SetHandler server-status` inside a `<Location "/server-status">` block with an access rule — `Require ip 10.0.0.0/8` or `Require local` in 2.4's authz syntax. The page is genuinely sensitive: it shows per-worker state, the client IP and full request line of everything in flight (query strings included), the vhosts being served, and server uptime. The trap is that `Require ip` tests the peer address of the TCP connection. When Apache sits behind another proxy, an ingress or a CDN, that peer is always the proxy — so a rule meant to admit only the internal network admits everyone, and it looks correct in review. Fixes, in order of preference: bind the status handler to an internal-only listener or vhost that external traffic cannot reach at all; or load mod_remoteip with `RemoteIPHeader` and `RemoteIPInternalProxy` so `Require ip` evaluates the real client; or require authentication rather than an address.
code
apacheconf · 18 lines# Trust only your own proxies, then evaluate the real client address
RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 10.0.0.0/8
<Location "/server-status">
SetHandler server-status
Require ip 10.0.0.0/8
</Location>
# Preferred where possible: reachable only on an internal listener
Listen 127.0.0.1:8081
<VirtualHost 127.0.0.1:8081>
ServerName status.internal
<Location "/server-status">
SetHandler server-status
Require all granted
</Location>
</VirtualHost>go deeper
Know that mod_status publishes a live view of what the server is handling, that it needs an explicit access rule, and that it should never be reachable from the internet.
Explain the SetHandler server-status configuration inside a <Location> block, what ExtendedStatus adds, and that Require is the 2.4 authorization syntax rather than the older Order/Deny form.
Show you know address-based rules evaluate the connection peer, so a proxy in front silently makes them fail open, and that you fix it by restoring the client address with a trusted-proxy list or by making the endpoint unroutable.
Generalize it: every address-based control in the estate is only as good as the trusted-proxy configuration behind it, so make that a platform-level default and require diagnostics endpoints to be unreachable by construction rather than by rule.
## What mod_status gives you mod_status renders the server's scoreboard as an HTML page: how many workers exist and what state each is in, and for the active ones, the client address, the vhost, the request line being served and how long it has been running. It also reports uptime, total requests and throughput. `ExtendedStatus` controls the detailed per-worker request information; as of 2.4 loading mod_status turns it on, and it costs a small amount of per-request bookkeeping. There is a machine-readable form at `/server-status?auto`, which is what monitoring agents scrape, and `?refresh=N` for a self-refreshing page. It is the fastest way to answer "what is this server doing right now" — particularly to see whether every worker is parked on the same slow upstream URL, which tells you the problem is downstream rather than in Apache. ## The configuration ```apacheconf <Location "/server-status"> SetHandler server-status Require local Require ip 10.0.0.0/8 </Location> ``` `SetHandler` is what turns a URL into the status handler — there is no file behind it, which is also why this must be a `<Location>` and never a `<Directory>` block. In 2.4 the access rules use `Require` (mod_authz_core / mod_authz_host); the `Order deny,allow` / `Deny from all` form you will still find in copied snippets is 2.2 syntax and needs `mod_access_compat` to work at all. Multiple `Require` lines are OR-ed by default. ## Why the address check fails behind a proxy `Require ip` compares against the remote address of the TCP connection Apache accepted. Put a CDN, an ingress controller or another reverse proxy in front, and that address is the proxy's — identical for every visitor on earth. The rule now either denies everyone (if the proxy's address is outside the allowed range) or, far more dangerously, admits everyone (if the proxy sits inside it, which internal proxies usually do). Nothing errors. The block reads correctly, `configtest` passes, and the status page — complete with in-flight URLs and client addresses — is public. This is the general shape of the problem, not a mod_status quirk: any address-based rule on a server behind a proxy is evaluating the wrong address. ## The three fixes, strongest first **Do not route to it.** The most robust answer is that external traffic cannot express the URL at all: serve the status handler from a separate vhost on a port bound to an internal interface, or on a management address the edge never forwards to. An access rule you never have to trust is better than one you do. **Restore the real client address.** mod_remoteip replaces the connection's remote address with one parsed from a forwarded header, but only for peers you nominate as trusted: ```apacheconf RemoteIPHeader X-Forwarded-For RemoteIPInternalProxy 10.0.0.0/8 ``` After that, `Require ip` and the `%a` log format see the client rather than the proxy. The trust list is the whole security of this: without it, or with it set too broadly, a client can simply send its own forwarded header and choose what address you evaluate. Use `RemoteIPInternalProxy` for infrastructure you operate and `RemoteIPTrustedProxy` for external ones, and list them precisely. **Require a credential.** An authenticated status endpoint does not care where the request came from. It is the right answer when the page must be reachable across a network you do not control. ## Blocking at the edge is not sufficient alone Teams often add a deny rule for `/server-status` at the CDN or ingress and consider it handled. That protects only the paths that traverse the edge. Anything that reaches the origin directly — another internal service, a misconfigured security group, a peer in the same network namespace — bypasses it entirely. Edge blocking is a useful extra layer, never the only one. ## Verify rather than review After configuring it, request the URL from outside and confirm you get a 403 rather than a page. Then request it from the address that is supposed to work and confirm you do get the page. Both halves matter: a rule that denies everyone including your monitoring agent is a different, quieter failure. This is a check worth running after any change to the edge, because the thing that breaks it is usually a change in front of Apache rather than in Apache.
- What specifically does an exposed /server-status page leak to an attacker?Live request lines including query strings — which can carry tokens, identifiers and search terms — plus client IP addresses, the vhost names configured, worker counts, uptime and traffic volume. Together that is a map of your internal URL space and a real-time feed of who is using it. Uptime and worker saturation also tell an attacker when the server is under load and how much headroom remains.
- What is the risk of enabling mod_remoteip without setting RemoteIPInternalProxy or RemoteIPTrustedProxy?The module will take the address from the forwarded header on any connection, so a client can send its own X-Forwarded-For and choose what address your Require rules and access logs see. That converts an address restriction into a self-service bypass. The trusted-proxy list is not optional configuration — it is the entire trust boundary that makes the header meaningful.
- Why must the status handler be configured in a <Location> block rather than a <Directory> block?Because SetHandler server-status produces a response with no file behind it — the URL never maps to a filesystem path, so no Directory section is selected for the request and the block would be inert. Location scopes by URL and is the correct container for handler-generated endpoints, the same reason proxied paths need Location too.
saying these in an interview costs you the question
- Assuming Require ip sees the real client behind a proxy
- Blocking /server-status only at the CDN and calling it done
- Treating the status page as harmless internal information
- Enabling mod_remoteip without a trusted-proxy list
- Configuring the status handler in a <Directory> block