Data and field notes

What Hits a Small Website Every Week: Our Server Logs

63.4% of the requests to our small site in 15 days were scanner probes. What they asked for, why nothing was exposed, and how to count them in your own logs.

Teal fiber optic patch cables plugged into a network rack
Photo: Brett Sayles on Pexels

Between 23 September and 7 October 2026, this site received 5,213 requests, about 353 a day. Of those, 3,304 (63.4%) were scanners asking for files the site has never had: .env files, WordPress login pages, .git folders, web shells. They came from 190 distinct addresses and asked for 1,059 distinct paths. We publish counts and never addresses.

No AdSense rule sits behind this guide, because Google says nothing about scanner traffic. We publish it for two practical reasons: it changes how you should read your own 404 report, and the one number that matters in it, how many probes got a 200 response, takes 5 minutes to check on your own site.

Two weeks of requests, in numbers

The window is 14.8 days on a site that is 3 months old. Client errors made up 3,336 of the 5,213 requests (64%), and 3,317 of those were plain 404s. Probe requests averaged about 223 a day, or roughly 1,560 a week.

The 190 addresses sent about 17 requests each on average, and each path was requested about 3 times on average, so the same paths were asked for again and again. The most requested were /wp-admin/install.php (191 requests) and /xmlrpc.php (44), followed by many variants of .env.

Compare that with the 404s we can explain. Ordinary clients asked for /favicon.ico 15 times, /llms.txt 8 times and /sitemap.xml 3 times, 26 in all, and we fixed those on 7 October. The 404 total and the probe total are close in size (3,317 and 3,304), but they are different counts, because a probe can be answered with something other than a 404, so do not subtract one from the other. The useful reading is simpler: on a small site, a raw 404 count tells you very little about broken links.

What they were looking for

We sorted the 3,304 probe requests into categories by the kind of file they asked for. The shares are of probe requests, and the daily figure is the average over the 14.8 days.

CategoryRequestsShare of probesPer day
Environment files (.env and variants)1,29539.2%88
WordPress paths55316.7%37
Cloud and credential files34210.4%23
PHP files and web shells33910.3%23
Config, backup and dependency files3179.6%21
Version control folders (.git, .svn)2407.3%16
Other1334.0%9
Admin interfaces and APIs461.4%3
AI tool configuration391.2%3

Environment files lead by a wide margin, and for a reason: a .env file usually holds a database password or an API key, so one exposed file can hand over a whole application. The 553 WordPress requests hit a site that has never run WordPress. Scanners do not check what you run, they ask everyone the same questions.

The smallest category is the one we found most telling. The 39 requests for AI tool configuration were for files such as .anthropic, .claude and .env.openai. We read that as scanners going where API keys now live, but that is our interpretation: the logs record the request and not the reason.

Why nothing was exposed

Not one probe received a 200. The reason is structural: this is a static site, with no WordPress, no PHP and no .env file in the folder the server publishes. Ask for /.env and you get a 404. There was nothing to find, so nothing was found.

That makes the 200 count the number to watch, not the probe count, which you cannot change. A probe that returns a 200 means one of two things: a real file is public, or your site answers every unknown URL with a page. The second is a soft 404, and the usual cause is a catch-all route in a single-page app or a CMS that renders its home page for any path. Google’s documentation describes a soft 404 as an error page served with a 2xx status, and Search Console reports it. Soft 404s and HTTP status codes shows how to return the right answer, and our AdSense checker requests a deliberately invented URL to see which one your server gives.

5 things to do on your own site

1. Refuse hidden files, except .well-known. Certificate renewal and other standards use /.well-known/, so everything else that starts with a dot can answer 404.

# nginx, inside the server block
location ~ /\.(?!well-known/) {
    return 404;
}
# Apache 2.4, in the virtual host or .htaccess (mod_alias)
RedirectMatch 404 "/\.(?!well-known/)"

2. Keep secrets, backups and repositories out of the published folder. Publish a build directory, never the project root, and exclude .git and .env* from your deploy. Then look for what slipped through:

find /var/www/example.com -type f \( -name '.env*' -o -name '*.bak' -o -name '*.old' -o -name '*.sql' -o -name '*.zip' -o -name '*.swp' -o -name '*~' \) -print
find /var/www/example.com -type d \( -name .git -o -name .svn \) -print

3. Check that unknown URLs answer 404. Both commands should print 404. A 200 means the problem above.

curl -s -o /dev/null -w '%{http_code}\n' https://example.com/.env
curl -s -o /dev/null -w '%{http_code}\n' https://example.com/no-such-page-7f3a

4. On WordPress, update and close what you do not use. Keep core, themes and plugins current, delete the readme.html in the root because it states the version, and refuse xmlrpc.php unless a plugin or the mobile app needs it. Jetpack and some apps do.

# nginx
location = /xmlrpc.php { return 404; }
# Apache 2.4
<Files "xmlrpc.php">
    Require all denied
</Files>

5. Slow the repeat offenders, and keep logs short. With fail2ban the principle is one rule: ban an address for a few hours after several requests to paths that return 404 within a few minutes. Treat it as noise reduction, not protection. Our protection is having nothing to find. Rotate logs and delete them on a schedule: ours go after about 14 days, as our privacy policy says.

Count it on your own log

These commands assume the combined log format that nginx and Apache both write by default. They print counts and paths and never addresses, so the output is safe to share. They stick to POSIX awk features, so they should run with mawk and gawk alike. First, extract the probes into a file with one line per request (category, status, path):

LOG=/var/log/nginx/access.log

awk -F'"' '
  { split($2, r, " "); split($3, s, " "); p = tolower(r[2])
    if (p ~ /\.env/)                                                      c = "environment files"
    else if (p ~ /\/\.(git|svn|hg)(\/|$)/)                                c = "version control"
    else if (p ~ /\/wp-(admin|login|content|includes|json)|xmlrpc\.php/)  c = "wordpress"
    else if (p ~ /\/\.(aws|ssh|docker|kube)(\/|$)|id_rsa|aws\/credentials/) c = "cloud and credentials"
    else if (p ~ /\.(php|phtml|phar)(\?|$)/)                              c = "php and web shells"
    else if (p ~ /\.(bak|old|sql|zip|tar|gz|swp)(\?|$)|composer\.(json|lock)|package\.json/) c = "backups and dependencies"
    else next
    printf "%s\t%s\t%s\n", c, s[1], p }' "$LOG" > probes.tsv

Then read it:

wc -l < "$LOG"                                         # all requests
wc -l < probes.tsv                                     # probe requests
cut -f1 probes.tsv | sort | uniq -c | sort -nr         # probes by category
cut -f3 probes.tsv | sort | uniq -c | sort -nr | head  # most requested paths
awk -F'\t' '$2 == 200' probes.tsv                      # probes answered 200: must print nothing
awk '{ print $1 }' "$LOG" | sort -u | wc -l            # distinct addresses, as a count only

Your categories will not match ours exactly. We sorted with a longer rule list and put some paths in “Other” or “Admin interfaces and APIs”, which these patterns do not cover. The rules also assume a site that does not legitimately serve PHP or composer.json. If yours does, the counts will include real traffic, and the 200 check will flag real pages.

What this data cannot tell you

One site, one 14.8-day window, a young domain, one hosting setup. We do not know how these shares compare with yours, how they move over a year or whether a larger site draws a different mix. The categories come from our own rules. The distinct address count is a count of addresses, not of operators or machines. Nothing here shows what any scanner intended.

What the numbers do support is narrower: the probes are routine, they ask everyone, and they only matter where something answers. Blocking them is not protection, but removing what they look for is. The next layers are security headers and a correct HTTPS setup, and the legitimate visitors in the same logs, Googlebot, Bing and the AI crawlers, are broken down in who crawls a small site.

Quick answers

Should I report or block the addresses that probe my site?

Reporting individual scanner addresses is rarely worth the time, and blocking them one by one does not scale. Remove what they are looking for, make unknown URLs answer 404, and use a rule-based ban such as fail2ban to cut the noise if the log volume bothers you.

Will scanner requests show up in Search Console?

No. Search Console reports URLs that Google itself found or crawled, so a scanner's guesses never appear there. Your server log is the only place you see them, which is why the counts in this guide come from logs and not from a Google report.

Sources

Who stands behind this

GreenlightMySite is run by Stéphane Cardon, a network and cybersecurity engineer. We write from running real sites through real reviews, and every technical claim here is either linked to a primary source or measured with our own tools.

About · Editorial policy · LinkedIn