Zum Inhalt springen

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

Aus Laub-Home Wiki

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 kein nft-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 (Variable F2B_DESTEMAIL).

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