Your 404 Logs Are a Security Report You’re Ignoring

Most WordPress developers install a redirect plugin, set up a few 301s, and never look at their 404 logs again. If they do, it’s for SEO, fixing broken links, cleaning up crawl errors. That’s the obvious use.

But there’s something else in those logs that almost everyone ignores.

Your 404 logs are a real-time map of who’s probing your site and what they’re looking for. Bots scanning for exposed environment files, credential leaks, config files, path traversal exploits, all of it shows up as 404s before it shows up as a breach.

I pulled the 404 logs from two of my own sites over the past week. Here’s what I found, and what I blocked at the CDN level so these requests never hit my server again.

The Numbers

Two WordPress sites. One week of data.

  • Site A: 1,388 total 404 requests across 1,118 unique URLs
  • Site B: 2,693 total 404 requests across 1,877 unique URLs

Over 4,000 requests that aren’t real users, aren’t search engines, and aren’t broken links. They’re automated scans looking for vulnerabilities.

The Attack Patterns

Every 404 log tells a story. Here are the five categories I found in mine.

1. Environment File Scanning (.env)

This is by far the most common pattern. Bots systematically scan every possible path where a .env file might exist:

/.env                    → 49 hits
/staging/.env            → 20 hits
/backend/.env            → 20 hits
/react/.env              → 15 hits
/.env.local              → 15 hits
/shared/.env             → 13 hits
/api/.env                → 12 hits
/.env.production         → 12 hits
/app/.env                → 10 hits
/server/.env             → 10 hits

And it doesn’t stop there. I found requests for .env.backup, .env.bak, .env.save, .env.old, .env_copy, .env_secret, .env~, .env.swp — over 80 different .env variations in total.

What they’re looking for: Database credentials, API keys, SMTP passwords, Stripe keys, AWS secrets. One exposed .env file = full access to your infrastructure.

2. Cloud Credential Harvesting

/.aws/credentials        → 17 hits
/.aws/config             → 5 hits
/.AwS/CrEdEnTiAlS        → 5 hits (case variation to bypass rules)
/.terraform/terraform.tfstate → 6 hits
/serviceAccountKey.json   → 4 hits
/credentials.json         → 5 hits

Notice the case variation on .AwS/CrEdEnTiAlS — that’s specifically designed to bypass naive pattern matching rules that only check lowercase. They’re hunting for AWS keys, GCP service accounts, and Terraform state files that might contain infrastructure secrets.

3. Config & Secret File Probing

/config/initializers/secret_token.rb  → 7 hits (Rails)
/config/storage.yml                   → 6 hits (Rails)
/application.yml                      → 5 hits (Spring Boot)
/docker-compose.yml                   → 4 hits
/sftp-config.json                     → 5 hits (Sublime SFTP)
/.vscode/sftp.json                    → 3 hits (VS Code SFTP)
/config.php.bak                       → 5 hits
/secrets.json                         → 6 hits
/sendgrid.env                         → 7 hits

Bots don’t care what framework you use. They scan for Rails, Laravel, Spring Boot, Node.js, Django — all in the same sweep. If you accidentally deployed a config file, they’ll find it.

4. WordPress-Specific Probing

/wp-config               → 3 hits
/wp-config~              → 1 hit
/wp-config.production    → 1 hit
/wp-configbak            → 1 hit

They’re looking for backup copies of wp-config.php — the file that contains your database credentials. Editor temp files (wp-config~), manual backups (wp-configbak), environment-specific copies. One careless cp wp-config.php wp-config.bak and you’ve exposed everything.

5. Path Traversal & Code Execution Attempts

/etc/passwd?raw??                               → 3 hits
/@fs/etc/passwd?import&raw??                     → 3 hits
/admin/config?cmd=cat /root/.aws/credentials     → 1 hit
/vendor/phpunit/phpunit/phpunit.xsd              → 2 hits
/pms?module=logging&file_name=../../~/.aws/credentials → 1 hit

These are active exploitation attempts, not just scanning. They’re trying to read system files, execute commands, and exploit known vulnerabilities in PHPUnit and other packages.

What to Do About It: Block at the CDN

Here’s the key insight: every one of these requests hit your server. Your WordPress installation processed them, your PHP executed, your database was queried for a 404 page, all for a bot that’s trying to hack you.

The fix is simple: block these patterns at the CDN level (Cloudflare, Fastly, CloudFront…) so they never reach your server. Zero load on your infrastructure, zero PHP execution, zero risk.

Cloudflare WAF Rules

Here are the rules I set up based on my actual 404 data:

Important: use lower() in all of then, attackers use case variations like .AwS/CrEdEnTiAlS to bypass naive rules.

Rule 1: Block .env file access

(lower(http.request.uri.path) contains ".env")
Action: Block

Rule 2: Block credential/config file access

(lower(http.request.uri.path) contains ".aws/credentials" or
 lower(http.request.uri.path) contains "terraform.tfstate" or
 lower(http.request.uri.path) contains "serviceaccountkey" or
 lower(http.request.uri.path) contains "docker-compose.yml" or
 lower(http.request.uri.path) contains "sftp-config" or
 lower(http.request.uri.path) contains "sftp.json" or
 lower(http.request.uri.path) contains "secrets.json" or
 lower(http.request.uri.path) contains "credentials.json" or
 lower(http.request.uri.path) contains "sendgrid" or
 lower(http.request.uri.path) contains "secret_token.rb" or
 lower(http.request.uri.path) contains "storage.yml" or
 lower(http.request.uri.path) contains "application.yml")
Action: Block

Rule 3: Block wp-config probing

(lower(http.request.uri.path) contains "wp-config" and
 not http.request.uri.path eq "/wp-admin/")
Action: Block

Rule 4: Block path traversal attempts

(lower(http.request.uri.path) contains "etc/passwd" or
 lower(http.request.uri.path) contains "phpunit" or
 lower(http.request.uri.path) contains "update.cgi" or
 http.request.uri contains "cmd=")
Action: Block

Rule 5: Block common backup/install directory probing

(lower(http.request.uri.path) eq "/old/" or
 lower(http.request.uri.path) eq "/new/" or
 lower(http.request.uri.path) eq "/backup/" or
 lower(http.request.uri.path) eq "/wordpress/" or
 lower(http.request.uri.path) eq "/wp/" or
 lower(http.request.uri.path) eq "/test/")
Action: Block

Cloudflare string comparison is case-sensitive by default. The lower() function converts the URI path to lowercase before comparison, so /.AwS/CrEdEnTiAlS gets matched by a rule checking for .aws/credentials. Without lower(), that request slips through.

Why CDN-Level Blocking Matters

When you block at the CDN:

  • The request never reaches your server
  • No PHP execution, no database query, no CPU usage
  • Your server resources go to real users, not bots
  • You reduce your attack surface to zero for known patterns
  • Logs stay clean, making it easier to spot new patterns

Blocking at the application level (WordPress plugins, .htaccess) still works, but the request already consumed server resources by the time it’s blocked. CDN-level blocking is the difference between a bouncer at the door and a bouncer inside the bar.

Make This a Habit

Pull your 404 logs once a month. Look for patterns. Add new Cloudflare rules.

The bots evolve. New scanning patterns appear. The .AwS/CrEdEnTiAlS case variation trick is a perfect example, someone specifically designed that to bypass lowercase-only rules.

Your 404 logs aren’t just broken links. They’re a free security audit. Read them.


The data in this post comes from real 404 logs collected over one week from two production WordPress sites. No IPs or identifying information from the attacking bots are included.


Enjoyed this post?

Subscribe for more content like this.