Web Server audit fixes

Content Exposure: fixing all 6 checks

The content exposure audit requests paths from the running server and searches the document roots for what you never meant to publish: directory listings, .git and .env, backups and status endpoints.

Only two things cause a skip here: no web server detected at all, and, for Exposed Backup Files, no root or DocumentRoot line in the config the audit could read. Every other check still returns a verdict when the loopback probe got no response, so on an unreachable host several of the PASSes rest on config text alone.

6
checks in this audit
1
rated high severity
2
need root or sudo
~14s
automated run time

Transcribed from the web-server-security-content-exposure audit script.

Every check

Find your finding

Listed in the order the audit runs them. Take the check name from the third field of the result line and open its page.

  • Directory Listing

    Soon

    Checks whether directory listing is enabled in the config and whether the server actually serves one

    MediumNo root4 results
  • Hidden File Exposure

    Soon

    Probes whether dotfiles such as .git and .env in the web root are reachable over HTTP

    HighNeeds root4 results
  • Exposed Backup Files

    Soon

    Checks the document roots for backup, editor and archive files that visitors could download

    MediumNeeds root4 results
  • Dangerous HTTP Methods

    Soon

    Probes whether TRACE is answered and whether the Allow header advertises write methods

    MediumNo root4 results
  • Exposed Status Endpoints

    Soon

    Checks whether server status and info endpoints are reachable without an access restriction

    MediumNo root3 results
  • Default Welcome Pages

    Soon

    Checks whether the stock welcome page or the distribution default vhost is still being served

    LowNo root3 results
Shared script behaviour

How the Content Exposure audit reads your server

Every check in this audit runs after the same preamble, and several of its results only make sense once you know what that preamble could and could not see.

.

All web-server scripts share one preamble. It detects nginx, Apache (apache2/httpd, APROOT = /etc/apache2 or /etc/httpd), Caddy and lighttpd. NGT is nginx -T output (effective config with every include expanded) when root, otherwise a concatenation of nginx.conf, conf.d/*.conf, sites-enabled/*. APC is the concatenated Apache config tree (main file, ports.conf, conf.d, conf-enabled, sites-enabled, mods-enabled, conf.modules.d), comments stripped; APM is apachectl -M (root) or the mods-enabled/*.load names. WROOTS are all root/DocumentRoot values. A live probe curls https://127.0.0.1/, http://127.0.0.1/, http://127.0.0.1:8080/ with the first real server_name/ServerName as Host:. hdr NAME reads a response header; code URL returns the HTTP status. Caddy and lighttpd are detected but their configs are not parsed, so most checks on those hosts rely on the live probe only. Messages ending in "(no live response, config only)" or "(default vhost only - no server_name found to probe)" mean the probe could not be used.

Why checks skip

A SKIP is never a pass

A skipped check verified nothing at all. The most common cause is privilege: 2 of the 6 checks in this audit need root for a complete result. Before every run, each script works out which of four privilege modes it is in.

The four privilege modes an audit script can run in, and what each one means for checks that need root
ModeHow it is reachedWhat it means for your results
rootThe audit runs as UID 0.Every privileged branch runs directly. No check skips for lack of permission.
nopasssudo -n true succeeds, so the account has passwordless sudo.Privileged commands run through sudo -n.
passA sudo password was supplied in the CtrlOps audit settings and accepted.Privileged commands run through sudo -S.
noneNone of the above worked.CAN_ROOT=0. Every privileged branch returns SKIP with a "Need root" message rather than guessing.

If many checks skip with a “need root” message, run the audit as root, grant the audit account sudo (with or without NOPASSWD), or supply the sudo password in the CtrlOps audit settings.

FAQ

Content Exposure questions

That WARN means the on-disk test found .git or .env inside a document root while the two probed URLs, /.git/HEAD and /.env, did not answer 200. Exactly those two paths are requested, only at the web root, and only against the first server_name, so another vhost or a deeper path is untested. Treat it as one config change away from exposure: move the file out of the document root and add a dotfile deny rule.
The check separates config from behaviour. It counts autoindex on in nginx and Options lines carrying Indexes without -Indexes in Apache, then looks for an Index of title in the probed response body. Config plus a live listing is the FAIL; config alone is the WARN you are seeing, which is exactly what Debian's stock Options Indexes on /var/www produces while every directory still has an index file. One missing index.html turns it into a real listing, so set autoindex off or Options -Indexes.
Exempt .well-known from the rule. A blanket dotfile deny also blocks /.well-known/acme-challenge/, which stops HTTP-01 validation and eventually expires the certificate. The nginx and Apache rules on the Hidden File Exposure page already carry that exception, and the same deny covers .svn, .htpasswd and editor leftovers that this check never probes for. Run certbot renew --dry-run afterwards, well before the next renewal is due.
Audit your fleet

Run all 6 Content Exposure checks, in one click

CtrlOps runs this audit over your existing SSH connection - no agents, no scripts to manage. $7/user/month after a 1 month free trial - no credit card required.

Start instantly· No credit card· No sneaky autorenewals