Every WordPress install has files that are supposed to be internal but occasionally end up uploaded to the document root and remain reachable over HTTP. The most dangerous: a .git/ folder, .env files, wp-config backups (.bak, .old, .save), bare SQL dumps, and readme.html.
Why this matters
Each of these files leaks something. .git/HEAD - if 'git pull' was used to deploy, the folder remains. Tools like 'git-dumper' reconstruct the entire commit history, including passwords removed long ago. .env - a Laravel/Node/Symfony environment file that sometimes lands in WordPress installs by accident. It typically contains DB_PASSWORD, API_SECRET, AWS credentials. If it has been read, rotate every secret in it immediately. wp-config.bak - an admin edited wp-config.php and saved a .bak first. The file is still served and contains the database password and salts. databases.sql / dump.sql - a database dump left in the root. It includes wp_users with password hashes. readme.html and license.txt - reveal the exact WordPress version, letting an attacker pick the right exploit. Bots scan the entire internet for these paths - commercial tools traverse millions of sites per hour.
How to detect
The audit performs HEAD/GET on a list of dangerous paths: /.git/HEAD, /.env, /wp-config.bak, /wp-config.php.bak, /wp-config-sample.php, /readme.html, /license.txt, /backup.sql, /database.sql, /.DS_Store, /composer.json, /composer.lock, /package.json. A 200 response means the file is exposed. Manually: open https://example.com/.git/HEAD in a browser - if you see content like 'ref: refs/heads/main' you have a problem. Try /readme.html as well. A quick command-line check:
for path in .git/HEAD .env wp-config.bak readme.html; do
echo "$path: $(curl -s -o /dev/null -w '%{http_code}' https://example.com/$path)"
doneHow to fix
- If .env, .git or wp-config.bak are present: this points to a flawed deploy process. Fix the process so it does not happen again. Add a .gitignore with 'wp-config.php' and '.env'. Switch to a deploy script (rsync with --exclude) instead of 'git pull' on the server.
- If .env was exposed: assume it has been read. Rotate every credential in it immediately - DB password, Stripe keys, SMTP keys, everything. Even 'maybe nobody read it' is wishful thinking - bots scan that path every day.
- Delete the files: SSH cd to the site root, then rm -rf .git wp-config.bak readme.html license.txt.
- Add server-level blocking for hidden files and dangerous extensions:
<FilesMatch "^\.|wp-config\.bak$|\.env$|\.git.*$|\.sql$|\.bak$"> Require all denied </FilesMatch> - For Nginx:
location ~ /\.(?!well-known) { deny all; return 404; } location ~* \.(bak|sql|env|log|old|save)$ { deny all; } - The deny rule must come before the location that hands requests to PHP, otherwise PHP runs first and the deny is bypassed.
- Re-test every path - they should return 403 or 404, not 200.
Common mistakes
Do not assume that if the file is empty or a stub it is fine - even a 200 with empty content tells an attacker something is there. Do not block only the specific path the audit found (.git/HEAD) and forget siblings (.git/config). Block the whole .git prefix. Do not delete the file without fixing the deploy pipeline - the next deploy will bring it back. Do not assume 'my host blocks this' without testing - default Cloudflare or Sucuri rules cover most but not all cases.
Verifying the fix
Re-run the audit - the check should turn green. Manually: curl -I for every suspicious path should return 403 or 404. If you rotated credentials after .env exposure, verify the site still connects to the database with the new password. Browse the site to confirm overly broad rules did not block legitimate assets.