Fail2ban-Dockerized: SSH und andere Dienste absichern per Docker Compose

Dockerized Fail2ban auf Basis von Debian 13 (Trixie): überwacht systemd-journald, blockiert Angriffe via iptables (nf_tables) und versendet E-Mail-Benachrichtigungen. Schnelle Einrichtung per Docker Compose und beispielhafter aktivierter SSH-Jail.
Überblick
fail2ban-dockerized härtet SSH und andere Dienste, indem fehlgeschlagene Login-Versuche aus den Logs (journald) erkannt und Quell-IPs zeitweise per Firewall geblockt werden. Konfiguration erfolgt über gemountete *.local-Dateien (jail.d, fail2ban.d) und optionale Mail-Benachrichtigungen via msmtp.
Voraussetzungen
- Linux-Host mit **systemd-journald** (lesbarer Journal-Pfad).
- Docker und Docker Compose installiert.
- Netzwerk-Modus **host** und Capabilities **NET_ADMIN/NET_RAW** erlaubt.
- (Optional) SMTP-Relay für E-Mail-Benachrichtigungen.
- Verzeichnisstruktur im Projekt (Beispiele):
data/fail2ban/jail.d/10-sshd.local
(SSH-Jail-Beispiel)data/fail2ban/jail.d/
(eigene Jails)data/fail2ban/fail2ban.d/
(globale Overrides)
Installation
1) Projekt Ordner anlegen und Konfiguration
mkdir /opt/fail2ban/
mkdir -p data/fail2ban/jail.d
cd /opt/fail2ban
nun legen wir die Konfigurationsdatei an und konfigurieren diese:
/opt/fail2ban/fail2ban.env
# ===============================
# fail2ban.env.example
# Copy to fail2ban.env and customize.
# ===============================
############################
# Fail2ban notifications
############################
# Address that will receive ban notifications from Fail2ban.
# Comma-separated list is allowed.
F2B_DESTEMAIL=fail2ban@domain.tld
############################
# msmtp (SMTP mailer)
############################
# SMTP server/relay to send mail through (e.g. your Mailcow host).
MSMTP_HOST=smtp.example.com
# SMTP port:
# - 587: STARTTLS (recommended)
# - 465: SMTPS (implicit TLS; set MSMTP_STARTTLS=off)
# - 25 : plain or TLS depending on your server policy
MSMTP_PORT=587
# Domain used for HELO/EHLO and to qualify local users (msmtp "maildomain").
MSMTP_MAILDOMAIN=example.com
# Envelope/From address used by msmtp.
# (Often the same as the authenticated user.)
MSMTP_FROM=root@example.com
# SMTP authentication username.
MSMTP_USER=root@example.com
# ---- Choose ONE of the following for the password ----
# Prefer a file (e.g., Docker secret) so the password isn't in plain env:
# MSMTP_PASSWORD_FILE=/run/secrets/msmtp_password
# Fallback: plain-text password (NOT recommended for production):
MSMTP_PASSWORD=changeme
# TLS settings:
# - MSMTP_TLS=on enables TLS support in general.
# - MSMTP_STARTTLS=on starts TLS on ports like 587 (recommended).
# Use MSMTP_STARTTLS=off for SMTPS/port 465 (implicit TLS).
MSMTP_TLS=on
MSMTP_STARTTLS=on
############################
# Timezone (for logs, dates)
############################
TZ=Europe/Berlin
2) SSH-Jail aktivieren
Nun erstellen wir die SSH Jail Datei:
/opt/fail2ban/data/fail2ban/jail.d/10-sshd.local
[sshd]
enabled = true
mode = aggressive
port = ssh
maxretry = 3
3) docker-compose.yml verwenden
Die Compose-Datei zieht das Image automatisch und mountet die benötigten Pfade:
/opt/fail2ban/docker-compose.yml
services:
fail2ban:
image: ghcr.io/alaub81/fail2ban-dockerized:latest
network_mode: host
cap_add:
- NET_ADMIN
- NET_RAW
restart: always
env_file:
- fail2ban.env
volumes:
# Fail2ban-Config + State
- ./data/fail2ban/fail2ban.d/:/etc/fail2ban/fail2ban.d:ro
- ./data/fail2ban/jail.d/:/etc/fail2ban/jail.d:ro
- data_fail2ban:/var/lib/fail2ban
# Host-Journal (volatile + persistent) and machine-id
- /run/log/journal:/run/log/journal:ro
- /var/log/journal:/var/log/journal:ro
- /etc/machine-id:/etc/machine-id:ro
logging:
# Fail2ban-Logs into Host-Journal
driver: journald
options:
tag: "fail2ban-DC"
volumes:
data_fail2ban:
Start:
docker compose up -d
4) Funktion prüfen
docker compose logs -f fail2ban
docker compose exec -it fail2ban fail2ban-client status
docker compose exec -it fail2ban fail2ban-client status sshd
5) Test Ban ausführen
nun können wir einen Testban ausführen, um die Mailbenachrichtigungen und um zu schauen ob die Firewall Regeln sauber erstellt werden:
# Erstellt einen Ban für die IP 203.0.113.77
docker compose exec fail2ban fail2ban-client set sshd banip 203.0.113.77
# zeigt alle firewall Regeln an, ganz unten sollte ein Eintrag für die IP sein
# IPv4
iptables -L
# IPv6
ip6tables -L
# und die IP wieder unbannen
docker compose exec fail2ban fail2ban-client set sshd unbanip 203.0.113.77
# zeigt alle firewall Regeln an, der Eintrag für die IP sollte weg sein
# IPv4
iptables -L
# IPv6
ip6tables -L
Weitere Konfiguration
- Eigene/zusätzliche Jails in:
data/fail2ban/jail.d/*.local
- Globale Fail2ban-Overrides (z. B. Loglevel) in:
data/fail2ban/fail2ban.d/*.local
- Für Container-Ports empfiehlt sich
chain = DOCKER-USER
, damit Regeln vor Docker-NAT greifen.
Troubleshooting
Schnell-Checks
Läuft der Container?
docker compose ps --filter name=fail2ban
docker compose logs -n 200 fail2ban
Status-Übersicht:
docker exec -it fail2ban fail2ban-client status
docker exec -it fail2ban fail2ban-client status sshd
Ketten/Regeln sichtbar?
docker exec -it fail2ban iptables -n -L --line-numbers | sed -n '1,120p'
docker exec -it fail2ban iptables -n -L f2b-sshd --line-numbers 2>/dev/null || true
Häufige Symptome & Behebung
„Keine Bans sichtbar“ / „No such chain f2b-…“
- Ursachen: Jail nicht aktiviert, falsches Backend/Logpfad, zu strenge Schwellwerte.
- Fix: In
data/fail2ban/jail.d/10-sshd.local
sicherstellen:
[sshd]
enabled = true
backend = systemd
port = ssh
findtime = 10m
maxretry = 5
bantime = 1h
banaction = iptables-multiport
chain = INPUT
Danach neu laden:
docker compose exec -it fail2ban fail2ban-client reload
„nft: not found“ in den Logs
- Ursache: Banaction
nftables
gewählt, aber Container hat keinnft
-CLI. - Fix: In deinem SSH-Jail
banaction = iptables-multiport
setzen (wie im Beispiel).
Journald wird nicht gelesen (keine Matches)
- Ursachen: Journal nicht gemountet oder keine Machine-ID sichtbar.
- Fix: Compose-Volumes prüfen:
- /run/log/journal:/run/log/journal:ro
- /var/log/journal:/var/log/journal:ro
- /etc/machine-id:/etc/machine-id:ro
Und im Jail backend = systemd
verwenden.
Regeln werden nicht angewendet (aber „Banned“ im Status)
- Ursachen: Fehlende Capabilities oder falscher Netzwerkmodus.
- Fix: Compose muss enthalten:
network_mode: host
cap_add:
- NET_ADMIN
- NET_RAW
Eigene IP versehentlich gebannt
- Fix: Whitelist ergänzen:
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 192.0.2.10 203.0.113.0/24
Danach:
docker compose exec -it fail2ban fail2ban-client reload
docker compose exec -it fail2ban fail2ban-client set sshd unbanip <deine-ip>
Mail-Benachrichtigungen kommen nicht an
- Checks:
docker logs -n 200 fail2ban | grep -i -E 'msmtp|sendmail|action.*mail'
- Fix:
- In
fail2ban.env
SMTP korrekt setzen (Host, Port, From, User). - Passwort bevorzugt per Datei:
MSMTP_PASSWORD_FILE=/run/secrets/msmtp_password
. - Aliase werden auf
root
gemappt (VariableF2B_DESTEMAIL
).
- In
SSH läuft auf anderem Port
- Fix: Im SSH-Jail anpassen:
[sshd]
port = 22,2222
Container-Dienste statt Host-SSH schützen Hinweis: Für per Docker veröffentlichte Ports eignet sich:
chain = DOCKER-USER
Dadurch greifen Regeln vor den Docker-NAT-Regeln.
Best Practices
- IPv6 mitdenken:
allowipv6 = auto
setzen, wenn IPv6 aktiv ist. - Logs im Blick:
docker compose logs -f fail2ban
- Regeln gezielt halten: Nur nötige Ports/Jails aktivieren;
findtime
/maxretry
auf deine Umgebung abstimmen.
Hinweise
- E-Mail-Benachrichtigungen werden über msmtp konfiguriert (siehe
fail2ban.env
). - Die Images werden wöchentlich aktualisiert.
- Das Image unterstützt IPv4 und – wenn aktiviert – IPv6 automatisch (via iptables/ip6tables).
- Das Ganze Projekt findet ihr bei GitHub: https://github.com/alaub81/fail2ban-dockerized