The /debug/pprof, server-status, and FPM pages streaming your live requests to strangers
curl https://yoursite.com/server-status?full and read what comes back. On most sites, a 404. On a surprising number, a live table of every request the server is handling right now: the client IPs, the exact URLs, the query strings with tokens still in them. You didn't build that page. Apache did, you flipped it on once to debug something, and it's been narrating your traffic to anyone who asks ever since.
These are diagnostic endpoints. Every serious runtime ships them, because when something is slow or leaking memory at 2am, you want a profiler and a status page. The trouble is that the same endpoint that helps you debug helps a stranger map your internals, and several of them default to no authentication at all. The fix is never to remove them. It's to make sure the only people who can reach them are you.
Four pages, four kinds of leak
They don't all leak the same thing, and knowing what each one hands over tells you which to care about first.
Go's /debug/pprof/ is the worst of the set, because one of its sub-endpoints, /debug/pprof/heap, is a full snapshot of the process's memory. That heap dump holds whatever the program was holding: JWTs mid-validation, the database DSN, API keys it loaded at boot, session tokens in flight. It's the same catastrophe as a Spring heap dump, served by Go's standard library the moment you import net/http/pprof and forget to gate it. The goroutine and profile endpoints alongside it leak your internal call graph for free.
Go's /debug/vars (expvar) returns a JSON blob that includes cmdline, the exact command line the process started with. That's os.Args verbatim, and it routinely carries flags like -db-dsn=postgres://user:pass@host, -jwt-secret=..., or -api-key=..., because passing secrets as flags is common and nobody expects the flags to be readable over HTTP.
Apache's /server-status with the extended view turned on (ExtendedStatus On, the default when mod_status loads) is a continuously-updating table of in-flight requests across every virtual host: method, full path, client IP, and how long each has been running. Leave it public and you've published a live tap of your own traffic, including the one-time tokens and reset links riding in query strings.
PHP-FPM's status page (/status, /fpm-status, /php-fpm-status) in its full view does the same for a PHP stack: per-process request method, full URI, the PHP_AUTH_USER if basic auth is in play, and the absolute path of the running script, which quietly discloses your server's directory layout.
Why these are open when you didn't mean to expose them
None of these required a deliberate "make this public" step. That's exactly why they slip through.
For Go, import _ "net/http/pprof" registers the handlers on http.DefaultServeMux as a side effect. If your app serves on the default mux, the debug routes are live with no further action, and a lot of tutorials show that import without mentioning it. For Apache, mod_status is commonly enabled, and the <Location /server-status> block ships with an access rule that people loosen to debug from home and never tighten back. For PHP-FPM, the status path is set in the pool config and the web server is supposed to block external access to it; when a reverse-proxy rule is missing or too broad, the pool answers the world. In each case the endpoint is doing precisely what it was built to do. The mistake is the audience.
Bind them inward, or wall them off
The principle is the same across all four: diagnostics should be reachable from your tooling and nowhere else. There are two clean ways to get there.
# Go: pprof + expvar on the public mux
import _ "net/http/pprof"
http.ListenAndServe(":8080", nil) // debug routes are live
# Apache: mod_status open to all
<Location /server-status>
SetHandler server-status
</Location>For Go, the strongest move is to never mount the debug handlers on your public listener at all: build a separate ServeMux, register pprof and expvar on it, and bind it to 127.0.0.1 or an internal interface that only your profiling tools can reach. For Apache and PHP-FPM, restrict the status locations to your own IP ranges, and better still keep them behind a VPN so there's no public-facing rule to get wrong.
Whether any of these answers an anonymous request is something a stranger settles with a single GET to a known path, so that's exactly how SurfaceCheckr checks it: it requests each diagnostic path from outside, with no credentials, and flags one only when the response carries that endpoint's unmistakable fingerprint, the Go pprof index text, the expvar cmdline array, the Apache server-status scoreboard, the FPM per-process block. It reads the structure that proves the page is live and stops; it won't pull your heap dump down or harvest the request table, only confirm that an outsider can. If you run a Spring stack, the Actuator endpoints that dump config and memory the same way are the close cousin of this finding, and a public Prometheus /metrics endpoint leaks the same class of internal detail through a different door.
Read next
- Is your framework's debug toolbar visible to the world?The admin panel you left unlocked
- Stack traces in production: what your error pages give awayThe admin panel you left unlocked
- Is your Swagger UI handing strangers a map of every API endpoint you have?The admin panel you left unlocked
- Headlamp, Dozzle, Kafka UI: a live feed of your internals on the public internetThe admin panel you left unlocked
Find it before someone else does.
Paste your domain. The grade and issue count are free, and you'll see in a couple of minutes exactly what's reachable from outside.