Stopping Web Server Abuse with Fail2Ban

At 4am yesterday morning I was awake. Luckily so, as I checked my email and saw a warning that my VPS had been running at 199% CPU for two hours. I went into my office and checked the logs. Someone had been hammering this site with HEAD requests since just after midnight with nearly 30,000 requests across different URLs in four hours.

If they’d hit the same page repeatedly, cached responses would have handled it. But by requesting thousands of distinct URLs, each request generated a fresh cache file, forcing PHP and the database to do real work every time. My small server was coping, but the load average was high.

I blocked the offending IP in .htaccess immediately. That stopped requests from reaching PHP, but I wanted a proper defence, something that would catch this kind of abuse automatically next time.

I already had Fail2Ban installed. I’d just never configured it for web server attacks. Time to do that.

Fail2Ban works by watching log files for patterns, then banning IPs that match too often. I needed two files: a filter to match Apache access log lines, and a jail to define the thresholds.

First, the filter at /etc/fail2ban/filter.d/apache-ratelimit.conf:

[Definition]

# Matches any request line in Apache access log (combined/common format)
failregex = ^<HOST> -."(GET|HEAD|POST|PUT|DELETE|OPTIONS) .

ignoreregex =

This matches every request in the log.

The jail decides what volume of requests counts as abuse. It lives at /etc/fail2ban/jail.d/apache-ratelimit.conf:

[apache-ratelimit]
enabled = true
filter = apache-ratelimit
logpath = /var/www/logs/access.log

# Triggers a ban after too many requests in a short window
maxretry = 500
findtime = 30

# Ban for 24 hours
bantime = 86400

# Use iptables to drop packets from banned IPs
banaction = iptables-multiport
port = http,https
protocol = tcp

# Allowlist trusted networks
ignoreip = 127.0.0.1

The logic: if a single IP makes more than 500 requests in 30 seconds then ban it for 24 hours. Adjust maxretry and findtime to suit your traffic patterns. Legitimate crawlers and real users won’t come close to these numbers, but an attacker blasting thousands
of requests will trip the threshold fast. These are not the numbers I’m using.

The ignoreip directive keeps trusted networks (monitoring services, your own IPs) from getting caught.

The changes to Fail2Ban are activated with the following command (as root):

systemctl restart fail2ban

And I verified it was running by checking the status:

fail2ban-client status apache-ratelimit

Within a couple of minutes, the offending IP appeared in the banned list. I checked the access log, and there was nothing more from that IP. The requests weren’t just being rejected by Apache; iptables was dropping the packets before they reached the web server at all.
That’s the key advantage over an .htaccess block: banned traffic never touches Apache.

A VPS with limited resources can’t absorb a flood of uncached requests. Caching helps with repeat visits to the same page, but an attacker who rotates URLs defeats that entirely. Fail2Ban shifts the defence from the application layer down to the network
layer, where it’s cheap to enforce.

If you run a small site on a VPS, configure fail2ban for your web server. It takes ten minutes and it works.

Anyway, I presume whoever did that attack is reading this post since they seem to like my blog so much. Why did you do it?

I’m Deleting Google Photos

popup dialog saying "Moving 3290 to bin..." with a progress meter

If you look at r/googlephotos on Reddit you’ll come across several posts from people who have had their accounts banned because of photos they have in their accounts. Most of the time it’s because of family photos of children. I have also seen posts from people who unwisely backed up their WhatsApp images folder, or backed up a drive without looking at what was on it first. Remember that researcher who lost their Google account because they had WWII Nazi documents in their Google Drive?

I have photos there going back to the year 2000, but I never regarded Google Photos as a backup service. It was a fabulous photo-sharing site, but I have barely used it since 2024 and now have my own self-hosted Immich server instead.

Last week I did a Google Takeout of everything, just in case, as it will also download any photos I saved from shared albums.

Today I went to the large photos and videos page and deleted almost every item off that page.

Unfortunately, there isn’t a “delete everything from Google Photos” button. You’ll have to visit photos.google.com and select an image and then scroll down, and down and down… and then with shift pressed, click on the last image to delete. It then may take some time for Google to mark the images as deleted.

Honestly, I expect that the automatic CSAM detection they’re using has already scanned every single photo I’ve uploaded so I’m not worried about that. I’m not uploading anything else there either, but I’ve been meaning to move off Google Photos entirely for a long time.

It is really difficult to do. In my head I know that I have a Takeout backup of everything. I know that all the original photos are on my computer at home, and they’re on Immich now, but selecting photos of familiar smiling faces, of birthday parties, of holidays and festivals, and then clicking DELETE is hard.

They’re my photos. It was nice knowing you Google Photos.

If you want an alternative, self-host Immich or use Pixel Union, a hosted version of Immich where you get 16GB free before you have to pay for more storage. It’s also hosted in the EU, which is even better! I have an account there. I uploaded a few recent photos in this album for you to see what it looks like.

The Enshittificator

He comes from a family with a proud tradition of enshittification, making the world worse.

Here is how platforms die: first, they are good to their users; then they abuse their users to make things better for their business customers; finally, they abuse those business customers to claw back all the value for themselves. Then, they die.

I call this enshittification, and it is a seemingly inevitable consequence arising from the combination of the ease of changing how a platform allocates value, combined with the nature of a “two sided market,” where a platform sits between buyers and sellers, holding each hostage to the other, raking off an ever-larger share of the value that passes between them.

Cory Doctorow, 2023.

You can also find it on YouTube.