Protect your GroupOffice server

Protect your GroupOffice server from vulnerability scanners with fail2ban

Merijn Schering Merijn Schering•October 08, 2026
Protect your GroupOffice server

If you run a self-hosted GroupOffice server, take a look at your Apache access log. Chances are you'll find bursts of requests like these:

groupoffice.example.com 203.0.113.45 - - [08/Oct/2026:09:03:06 +0200] "GET /.env HTTP/1.1" 404 236
groupoffice.example.com 203.0.113.45 - - [08/Oct/2026:09:03:06 +0200] "GET /.git/config HTTP/1.1" 404 236
groupoffice.example.com 203.0.113.45 - - [08/Oct/2026:09:03:06 +0200] "GET /.aws/credentials HTTP/1.1" 404 236
groupoffice.example.com 203.0.113.45 - - [08/Oct/2026:09:03:07 +0200] "GET /api/uploads/%2e%2e%2f%2e%2e%2f.env HTTP/1.1" 404 236
groupoffice.example.com 203.0.113.45 - - [08/Oct/2026:09:03:13 +0200] "GET /phpinfo.php HTTP/1.1" 404 236

These are automated vulnerability scanners. They probe every server they can find for leaked configuration files, cloud credentials, Git repositories, debug endpoints and path traversal bugs. One scanner we saw recently sent about 250 requests in under 10 seconds.

None of these requests succeed against GroupOffice, but the sheer volume can still hurt. Every request occupies an Apache worker, and when a few scanners hit at once your server can become slow or unresponsive for the people actually using it.

In this post we'll set up fail2ban on Debian to recognize these scanners in the Apache logs and block them at the firewall within seconds.

The approach

We'll create two fail2ban jails:

  1. apache-badpaths watches the access log and bans an IP after just 3 requests for paths no legitimate GroupOffice user ever requests, such as .env files, /proc/self/environ, .git, credentials files and ../ path traversal.
  2. apache-phpnotfound watches the error log and bans an IP that requests many PHP scripts that don't exist. This catches scanners probing paths that aren't on our list.

Why not simply ban on every 404 in the access log? Because GroupOffice clients produce legitimate 404s too. CalDAV, CardDAV and WebDAV clients regularly receive 404 responses as part of normal syncing, and GroupOffice itself returns a 404 from index.php when, for example, a temporary download has expired. Counting those would sooner or later ban your own users.

The Apache error log solves this neatly. When a request targets a PHP script that doesn't exist on disk, Apache writes a line like this:

[client 203.0.113.45:51046] script '/var/www/groupoffice/info.php' not found or unable to stat

GroupOffice's own 404 responses and DAV traffic never produce this line, so we can count it without maintaining any exclusion list.

Install fail2ban

sudo apt install fail2ban

Check your access log format

The bad paths filter assumes the virtual host name comes before the client IP, as in Debian's vhost_combined format (%v:%p %h %l %u %t "%r" %>s %O ...) used for /var/log/apache2/other_vhosts_access.log:

groupoffice.example.com:443 203.0.113.45 - - [08/Oct/2026:09:03:06 +0200] "GET /.env HTTP/1.1" 404 236

If your access log uses the plain combined format with the client IP at the start of the line, remove the \S+ right after the ^ in the failregex line.

Filter 1: obvious exploit probes

Create /etc/fail2ban/filter.d/apache-badpaths.conf:

[Definition]
# Requests for secrets, debug tools and path traversal. Case-insensitive.
# Note: a literal % must be written as %% in fail2ban config files.
failregex = ^\S+ <HOST> \S+ \S+ .*"[A-Z]+ [^"]*(?i:/\.env|\.env\.|/\.git|/\.aws|/\.ssh|/\.npmrc|/\.dockerenv|id_rsa|id_ed25519|proc/self|\.\./|\.\.%%2f|%%2e%%2e|%%2eenv|/@fs/|__vite|terraform\.tfstate|serviceaccount|credentials\.json|service-account\.json|firebase-admin|/actuator|phpinfo|/pi\.php|/info\.php|/test\.php|app_dev\.php|_profiler|_ignition|_debugbar|telescope/|elmah\.axd|trace\.axd|wp-login|wp-admin|xmlrpc\.php|cgi-bin/)[^"]*"

ignoreregex =

Feel free to extend the list with patterns you see in your own logs.

Filter 2: PHP scripts that don't exist

Create /etc/fail2ban/filter.d/apache-phpnotfound.conf:

[Definition]
# Apache logs this only when the requested PHP script does not exist on disk.
failregex = \[client <HOST>(?::\d+)?\] .*script '[^']*' not found or unable to stat

ignoreregex =

The jails

Create /etc/fail2ban/jail.d/apache-scanners.conf:

[DEFAULT]
# Add your office and monitoring IPs so you never lock yourself out
ignoreip = 127.0.0.1/8 ::1

# Repeat offenders get increasingly longer bans
bantime.increment = true
bantime.maxtime   = 4w

[apache-badpaths]
enabled  = true
backend  = auto
port     = http,https
filter   = apache-badpaths
logpath  = /var/log/apache2/*access*.log
maxretry = 3
findtime = 10m
bantime  = 1d

[apache-phpnotfound]
enabled  = true
backend  = auto
port     = http,https
filter   = apache-phpnotfound
logpath  = /var/log/apache2/*error*.log
maxretry = 10
findtime = 1m
bantime  = 6h

By default a ban blocks only ports 80 and 443. If you'd rather cut banned scanners off from your server entirely, add banaction = nftables-allports to both jails.

Test before you activate

fail2ban-regex runs a filter against an existing log file without banning anything:

sudo fail2ban-regex /var/log/apache2/other_vhosts_access.log /etc/fail2ban/filter.d/apache-badpaths.conf
sudo fail2ban-regex /var/log/apache2/error.log /etc/fail2ban/filter.d/apache-phpnotfound.conf

Add --print-all-matched to see exactly which lines match. Check that no normal GroupOffice traffic from your users appears in the output.

Activate

sudo systemctl reload fail2ban
sudo fail2ban-client status apache-badpaths
sudo fail2ban-client status apache-phpnotfound

The status output shows the number of failures counted and the IPs currently banned.

Managing bans

fail2ban stores its bans in a database, linked to the jail name. When you change a filter later and reload, active bans stay in place and are restored for their remaining time. You don't need a full restart; reloading a single jail is enough:

sudo fail2ban-client reload apache-phpnotfound

If a legitimate user does get banned, you can lift the ban right away:

sudo fail2ban-client set apache-phpnotfound unbanip 203.0.113.45

To see which IPs currently have the most connections to your server, for example during an attack, this one-liner helps:

ss -tn state established | awk 'NR>1 {sub(/:[0-9]+$/,"",$4); print $4}' | sort | uniq -c | sort -rn | head -20

Wrapping up

With these two jails in place, a scanner typically gets banned within a second or two of starting its run, long before it can tie up your Apache workers. Keep an eye on fail2ban-client status for the first few days, add your own IPs to ignoreip, and extend the bad paths list as new scanning patterns show up in your logs.

Running GroupOffice yourself and have questions about securing your installation? Contact us.

Twitter LinkedIn GitHub Mastodon