Skip to content
Back to writing

~/writing field note

  • coolify
  • fail2ban
  • security
  • self-hosting
  • ubuntu

How To Secure Coolify Server with Fail2Ban

Mustafa Ramx pixel avatarBy Mustafa Ramx15 June 20256 min readUpdated 9 Nov 2025
Cover image for How To Secure Coolify Server with Fail2Ban
On this page5 sections
  1. Why Fail2Ban?
  2. Step-by-step setup (tested on Coolify + Ubuntu)
  3. Protect your websites — enable logging in Traefik
  4. Merge Fail2Ban SSH bans into Traefik middleware
  5. Final thoughts

Self-hosting Coolify on a VPS is awesome — it gives you full control and zero limits compared to Vercel or Netlify. But that control comes with responsibility. One of the biggest risks is leaving your server exposed to brute-force, port scans, and bots hammering /wp-login.php or similar paths and admin panels.

In this guide, I’ll show you exactly how I secured my Coolify server using Fail2Ban, even when Traefik is handling all the reverse proxy traffic.

Why Fail2Ban?

Fail2Ban monitors logs for suspicious patterns (e.g. 404s, failed SSH logins) and blocks offending IPs by injecting firewall rules — using iptables or nftables.

Step-by-step setup (tested on Coolify + Ubuntu)

Install Fail2Ban

sudo apt update
sudo apt install fail2ban -y

To confirm Fail2Ban is ready and running on your Coolify host, check it with:

sudo systemctl status fail2ban

You should see: Active: active (running).

Configure Fail2Ban

Create a local configuration file to override the defaults:

sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local

In the [DEFAULT] section, adjust parameters like:

bantime   = 1d
findtime  = 10m
maxretry  = 5

These control how long an IP is banned, the time window for counting failures, and the number of retries allowed.

Enable SSH protection

Still in jail.local, ensure the [sshd] section is enabled — by default it already looks like this:

[sshd]
enabled  = true
port     = ssh
logpath  = %(sshd_log)s
backend  = %(sshd_backend)s

This configuration helps protect against brute-force SSH attacks.

Restart Fail2Ban

Apply your changes:

sudo systemctl restart fail2ban

Check if the sshd jail is active:

sudo fail2ban-client status

You should see something like:

Status
|- Number of jail: 1
`- Jail list: sshd

For more details (Currently banned IPs, Log path, Max retry, find time, etc.), run:

sudo fail2ban-client status sshd

Great. At this point your Coolify server is already protected from brute-force SSH attacks. Fail2Ban is installed, active, and monitoring login attempts in real time. Now let’s take it a step further — it’s time to secure your web layer too. To do that, we need to enable HTTP access logging in Traefik so Fail2Ban can watch and act on suspicious traffic hitting your websites.

Protect your websites — enable logging in Traefik

Edit your Traefik config to enable access logs:

      - '--accesslog=true'
      - '--accesslog.filepath=/traefik/access.log'
      - '--accesslog.format=json'

Restart Traefik to apply.

Traefik access-log flags applied in the Coolify-managed compose command

This will log access in structured JSON to /data/coolify/proxy/access.log (inside the container: /traefik/access.log).

To confirm it’s working:

tail -f /data/coolify/proxy/access.log

Create a Fail2Ban filter for Traefik

Create /etc/fail2ban/filter.d/traefik-http.conf:

[Definition]
failregex = .*"ClientHost":"<HOST>".*"OriginStatus":(404|403|401|429|502|503|504)
ignoreregex =

Create the jail config

Edit or create /etc/fail2ban/jail.d/traefik-http.conf:

[traefik-http]
enabled   = true
backend   = auto
filter    = traefik-http
logpath   = /data/coolify/proxy/access.log
maxretry  = 5
findtime  = 60
bantime   = 3600
banaction = nftables

Restart Fail2Ban and check status:

sudo systemctl restart fail2ban
sudo fail2ban-client status traefik-http

To check Fail2Ban logs, run:

sudo cat /var/log/fail2ban.log

Then Fail2Ban uses the default nftables action, which:

  • creates a set in inet f2b-table
  • injects rules into inet filter INPUT
  • drops IPs matching the set

To check the blocked rules in the firewall:

sudo nft list ruleset | grep reject

Or check the actual nftables sets:

sudo nft list set inet f2b-table addr-set-traefik-http
sudo nft list set inet f2b-table addr6-set-traefik-http

Now your Coolify server is protected with real-time IP banning for HTTP/s threats using Fail2Ban and nftables. This setup actively blocks bots and attackers trying to brute-force or probe your apps through Traefik. But why stop there? Let’s level up and extend those bans — so if someone hits your SSH, they also get blocked from all your websites.

Merge Fail2Ban SSH bans into Traefik middleware

Fail2Ban is great at blocking brute-force attacks on services like SSH. But what if you want to propagate those bans to your web layer too — for example, block the same IPs at the Traefik level?

Yes, it’s possible — and here’s how we did it in our Coolify setup.

Use the Fail2Ban Traefik plugin (built-in middleware)

We will use the official Traefik plugin github.com/tomMoulard/fail2ban — this allows us to block IPs directly from within Traefik using a middleware. More info about the plugin:

https://plugins.traefik.io/plugins/628c9ebcffc0cd18356a979f/fail2-ban

To install the plugin, add this to the Coolify-managed Docker Compose command for Traefik:

- '--experimental.plugins.fail2ban.modulename=github.com/tomMoulard/fail2ban'
- '--experimental.plugins.fail2ban.version=v0.8.3'

We also enable this middleware on both HTTP and HTTPS routes — add these inside the Traefik configuration file:

labels:
  - traefik.http.routers.traefik.middlewares=fail2ban@file
  - traefik.https.routers.traefik.middlewares=fail2ban@file

So even if attackers try hitting /admin or /wp-login.php on HTTPS — they’ll still get blocked.

The Coolify-managed Traefik compose with the Fail2Ban plugin flags highlighted

This enables the plugin. Then we create a middleware using the plugin that reads denied IPs from a file — name it fail2ban.yml:

http:
  middlewares:
    fail2ban:
      plugin:
        fail2ban:
          logPath: /traefik/access.log
          maxretry: 5
          findtime: 60
          bantime: 600
          statuscodes:
            - 401
            - 403
            - 404
            - 429
            - 500
            - 502
            - 503
            - 504
          denylist:
            files:
              - "/traefik/denylist.txt"

The fail2ban Traefik plugin middleware configured in fail2ban.yml

Sync Fail2Ban bans to Traefik

We write a small cronjob or script to export current Fail2Ban bans (e.g. from the sshd jail):

fail2ban-client status sshd | grep 'Banned IP list' | cut -d ':' -f2 | tr ',' '\n' > /data/coolify/proxy/denylist.txt

The Traefik plugin will then automatically pick up new IPs from that file and apply them instantly.

And we’re done. One source of truth (Fail2Ban), multiple layers of protection (SSH + Traefik). Super flexible, and easy to maintain.

Using Cloudflare or Tunnels?

If you’re using Cloudflare or Cloudflared Tunnels, make sure your Traefik setup trusts forwarded headers like X-Forwarded-For — so you can log the real IP address behind the tunnel or proxy. Otherwise, Fail2Ban or the Traefik plugin might end up banning 127.0.0.1 or your tunnel instead of the attacker 🚫

If yes, include the following in your Traefik configuration file:

- '--entrypoints.http.forwardedHeaders.trustedIPs=10.0.0.0/8'
- '--entrypoints.https.forwardedHeaders.trustedIPs=10.0.0.0/8'
- '--entrypoints.http.forwardedHeaders.insecure=true'
- '--entrypoints.https.forwardedHeaders.insecure=true'

This lets Traefik read the real client IP even when traffic comes from a reverse proxy.

If you don’t use Cloudflare or any proxy in front of your server — you can safely remove those flags for extra security.

What’s new (Nov 2025): Updated the failregex example to match numeric status codes (e.g. "OriginStatus":404) instead of quoted strings — improves accuracy.

Final thoughts

Fail2Ban works on Coolify, but CrowdSec works smarter

While the old-school Fail2Ban can secure both SSH and HTTP on your Coolify + Traefik setup, it needs extra scripting to fully protect Docker traffic. It works — but it’s not built for modern stacks like Coolify.

CrowdSec, on the other hand, is designed for today’s web:

  • Native Traefik Bouncer — no hacks, no custom scripts
  • Real-time ban propagation across services
  • Works even behind Cloudflare or Cloudflared tunnels
  • Maintains a global threat intelligence network

Fail2Ban is local, old-school, IP-based, and needs work to be Cloudflare-compatible.

CrowdSec is cloud-native, collaborative, and modern.

So while this guide helps you harden your Coolify server with Fail2Ban, if you want a cleaner, more powerful security layer, go with CrowdSec — it just fits better. Check this guide: Secure Your Coolify Server & Websites with CrowdSec and Traefik.

Found this useful or inspiring? You can support my work — buy me a coffee. Every little bit helps keep the magic alive, and my brain awake 😅

// share this note