672 Requests in a New Server's First 4.5 Hours — Zero Were Human
What happened The day I spun up a new instance, attached a static IP, and switched DNS over, I got curious: who is actually using this dev server right now? So I aggregated the access log for the first four and a half
What happened
The day I spun up a new instance, attached a static IP, and switched DNS over, I got curious: who is actually using this dev server right now? So I aggregated the access log for the first four and a half hours, grouped by IP.
$ awk '{print $1}' /var/log/httpd/access_log | sort | uniq -c | sort -rn
672 requests total. The breakdown:
| Source | Count | What it was |
|---|---|---|
| Me (post-migration QA) | 119 | Manual checks |
| One cloud-provider IP | 421 | A vulnerability scan — in 17 seconds |
| Certificate authority | 4 | Domain validation |
| ~20 crawlers | ~120 | Hit / and the login page, then leave |
The only human was me. Actual users: zero. The 421-request scan was the eye-opener: in seventeen seconds it swept through every "commonly forgotten file" on its list — /wp-config.php.bak, /.env, /graphql, /wrangler.toml — and got 404 or 403 for all of them. It was probing a non-WordPress server for WordPress config files. Scanners don't profile their target; they just run the whole list, top to bottom.
Where I got tripped up
1. Scans arrive with your domain name within hours of the DNS switch
My first reaction: "Scanning a raw IP, fine — but how do they already know the domain?" The answer is Certificate Transparency logs. When a TLS certificate is issued, the domain name is written to a public log. Scanners watch those logs, so "new certificate issued" equals "new server just appeared" in near real time. "Nobody knows this URL yet, so it's safe" simply isn't a thing.
2. You cannot filter scanners by User-Agent
The scanner's UA was a perfectly ordinary Mozilla/5.0 ... browser string. UA-based filtering is useless. Two signals actually separated bots from humans:
- Requests per second — no human opens 421 pages in 17 seconds
-
Ratio of nonexistent paths — legitimate users don't stumble into
/wp-config.php.bak
# Status-code distribution per IP
$ awk '{print $1, $9}' access_log | sort | uniq -c | sort -rn | head -20
3. "Nothing in last" does not mean "nobody logged in"
I checked SSH history with last, saw nothing suspicious, and almost called it a day. But key-based non-interactive execution (ssh host 'command') leaves no entry in last. Absence of a login record is not absence of a login — worth keeping in mind before you write "no unauthorized access" in a report.
4. A zero-byte ssl_access_log scared me
The HTTPS log was empty and for a moment I thought logging was broken. In reality, the virtual-host CustomLog directive funneled both HTTP and HTTPS into the single access_log file. When a log looks "missing," check where the config actually writes before panicking.
Verification
For the 421 scan requests, the one thing I made sure to check was whether any of them got a 200.
$ grep "<scanner-ip>" access_log | awk '$9==200 {print $7}' | sort | uniq -c
The only 200s were redirects to the login page; not a single request reached a real file. Only after seeing that can you say "we were scanned, but nothing landed." Counting requests isn't log review — knowing what returned 200 is.
Takeaways
- The default state of a public server isn't "unused" — it's "used only by bots"
- Judge whether a server is idle by the number of human requests, not total requests
- Your domain goes public the moment a certificate is issued. Lock down pre-launch servers like production
- Identify scanners by speed and nonexistent-path ratio, never by User-Agent
The first visitor to any new server is not a customer. It's a scanner looking for your .env. Lock the doors on day one accordingly.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.