fail2ban instellen en brute-force-aanvallen automatisch blokkeren

Gepubliceerd op 15 min leestijd

Zo configureert u fail2ban netjes via jail.local en krijgt u de sshd-jail aan de praat op alle vier de actuele LTS-distributies. Daarnaast leest u hoe u blokkades controleert en weer opheft, en welke fouten u anders ophouden.

Elke server met een openbaar IPv4-adres krijgt binnen enkele minuten na de eerste uitrol al inlogpogingen op poort 22. Dat is geen gerichte aanval, maar achtergrondruis. fail2ban leest de logregels van die mislukte pogingen mee en laat de firewall het bron-IP voor een vastgelegde tijd blokkeren. De tool is in alle hier behandelde distributies als pakket beschikbaar en in een paar minuten ingericht. De valkuilen zitten elders: op Debian 12 start fail2ban graag met een configuratiefout, op sommige systemen blokkeert het schijnbaar zonder dat er werkelijk een pakket wordt verworpen, en één onbezonnen regel in jail.local sluit u van uw eigen server buiten.

Dit artikel behandelt precies die plekken, voor Debian 13, Debian 12, Ubuntu 24.04 LTS en Ubuntu 22.04 LTS.

Waarom jail.local en nooit jail.conf

Het bestand /etc/fail2ban/jail.conf is eigendom van het pakket. Bij elke update kan het worden overschreven, en dan vraagt dpkg in het gunstigste geval na, in het ongunstigste geval (een onbeheerde updateronde) helemaal niet. Uw eigen aanpassingen horen daarom in /etc/fail2ban/jail.local. Dat bestand bestaat na de installatie nog niet, u maakt het zelf aan, en het mag alleen de waarden bevatten die u werkelijk wijzigt.

Belangrijk is de volgorde waarin fail2ban leest. De manpagina jail.conf(5) noemt die uitdrukkelijk:

  1. jail.conf
  2. jail.d/*.conf in alfabetische volgorde
  3. jail.local
  4. jail.d/*.local in alfabetische volgorde

Later gelezen bestanden winnen. Concreet betekent dat: uw jail.local overschrijft ook de instellingen uit /etc/fail2ban/jail.d/defaults-debian.conf die het distributiepakket meebrengt. Veel handleidingen beweren het tegendeel en raden daarom een eigen bestand in jail.d/ aan. Nodig is dat niet. jail.d/ is alleen zinvol wanneer u jails via configuratiebeheer afzonderlijk wilt uitrollen.

Wat uw distributie al vastlegt

Het grootste verschil tussen de vier systemen zit niet in fail2ban zelf, maar in dat ene bestand dat het pakket in /etc/fail2ban/jail.d/defaults-debian.conf neerzet. Bekijk het eerst:

cat /etc/fail2ban/jail.d/defaults-debian.conf
Systeemfail2banInhoud van defaults-debian.conf
Debian 131.1.0banaction = nftables, banaction_allports = nftables[type=allports], voor [sshd] bovendien backend = systemd, journalmatch = _SYSTEMD_UNIT=ssh.service + _COMM=sshd en enabled = true
Debian 121.0.2alleen [sshd] en enabled = true
Ubuntu 24.041.0.2banaction = nftables, banaction_allports = nftables[type=allports], backend = systemd, daarnaast [sshd] met enabled = true
Ubuntu 22.040.11.2alleen [sshd] en enabled = true

Daaruit volgt bijna al het overige. Debian 13 en Ubuntu 24.04 lezen standaard het systemd-journal en blokkeren via nftables. Debian 12 en Ubuntu 22.04 vallen terug op de instellingen uit jail.conf, dus banaction = iptables-multiport en backend = auto. En auto betekent uitdrukkelijk niet "zoek zelf maar het juiste uit". Het commentaar in jail.conf zegt dat auto achtereenvolgens pyinotify en polling probeert. Beide zijn bestandsgebaseerde methoden. De waarde auto kiest nooit het journal. Zonder het bestand /var/log/auth.log draait de sshd-jail daar in het luchtledige.

Installatie en de eerste controle

apt update
apt install -y fail2ban

Op Debian 12 en Ubuntu 22.04 komt er een pakket bij wanneer u het journal in plaats van een logbestand wilt uitlezen:

apt install -y python3-systemd

Op Debian 13 is python3-systemd een harde afhankelijkheid van het pakket en al aanwezig. Op Debian 12 is het slechts aanbevolen, en dat is op dat systeem de meest voorkomende foutbron. Controleer daarna versie en toestand:

apt-cache policy fail2ban
fail2ban-client --version
systemctl status fail2ban --no-pager

Staat hier al active (running), dan is het halve werk gedaan. Staat er failed, ga dan meteen door naar het hoofdstuk over de foutmeldingen.

Een eigen jail.local opbouwen

Maak /etc/fail2ban/jail.local aan. Deze versie werkt op alle vier de systemen, mits python3-systemd geïnstalleerd is:

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 203.0.113.7
bantime  = 1h
findtime = 10m
maxretry = 5

bantime.increment = true
bantime.factor    = 2
bantime.maxtime   = 7d

banaction = nftables
banaction_allports = nftables[type=allports]

[sshd]
enabled  = true
backend  = systemd
port     = ssh
maxretry = 4
bantime  = 2h

De betekenis van de waarden in gewone taal: findtime is het schuivende tijdvenster, maxretry het aantal mislukte pogingen daarbinnen en bantime de blokkeerduur. Vier mislukte pogingen in tien minuten leiden hier tot twee uur blokkade. Tijdsaanduidingen begrijpen achtervoegsels als m, h, d en w, een kaal getal betekent seconden. Een bantime van -1 blokkeert permanent.

Het interessante deel is bantime.increment. Daarmee verdubbelt fail2ban de blokkeerduur bij elke terugval van hetzelfde IP-adres, met bantime.maxtime als bovengrens. Een eenmalige typefout van een echte gebruiker kost twee uur, een hardnekkige bot komt na een paar ronden op een week uit. Deze functie bestaat sinds fail2ban 0.11 en is dus ook op Ubuntu 22.04 beschikbaar. Om die functie over herstarts heen te laten werken, moet de database de historie bewaren. Dat regelt dbpurgeage in /etc/fail2ban/fail2ban.conf, standaard één dag. Zet u bantime.maxtime = 7d, verhoog dan dbpurgeage in een fail2ban.local navenant.

Twee valkuilen in dit blok. Ten eerste port = ssh: dat wordt via /etc/services herleid tot poort 22. Draait uw SSH-dienst op 2222, dan moet daar port = 2222 staan, anders ontstaat er een firewallregel voor een poort waarop niemand aanklopt. Ten tweede ignoreip: zet daar het vaste IP-adres van uw kantoor in, maar nooit een heel providernetwerk. En vertrouw daar niet blindelings op. De waarde ignoreself staat sowieso standaard op true en beschermt de eigen server-IP's.

Afwijkingen per systeem

Op Debian 12 hebt u twee gelijkwaardige wegen. Ofwel u blijft bij backend = systemd en installeert python3-systemd, ofwel u installeert rsyslog, laat backend weg en werkt met /var/log/auth.log. Daarnaast doet u er op Debian 12 goed aan de banaction bewust te zetten. Het pakket beveelt nftables of iptables aan, en apt installeert daarvan doorgaans alleen nftables. De standaardwaarde uit jail.conf luidt echter iptables-multiport. Op een kale installatie grijpt fail2ban dan naar een binary die er helemaal niet is.

Op Ubuntu 22.04 zit rsyslog in de standaard serverinstallatie, bestaat /var/log/auth.log en draait de jail zonder dat u iets hoeft te doen. Wilt u daar niets omzetten, laat dan backend en banaction in uw jail.local gewoon weg. fail2ban 0.11.2 kent de nftables-actie wel, maar de weg via iptables-multiport is op dit systeem de meest gangbare.

Op Debian 13 en Ubuntu 24.04 valt uw jail.local samen met de instellingen van het pakket. Dat is geen bezwaar, het maakt de configuratie alleen expliciet en daarmee leesbaar.

Toepassen en controleren of het werkelijk aanslaat

De configuratie inlezen zonder bestaande blokkades te verliezen:

fail2ban-client reload

Op Debian 12 en Ubuntu 24.04 meldt fail2ban 1.0.2 bij het starten en bij het herladen de waarschuwing 'allowipv6' not defined in 'Definition'. Die is onschuldig, de dienst werkt gewoon normaal door.

Een systemctl restart fail2ban is zelden nodig en zet de lopende blokkades terug op nul. Daarna de statusopvragingen:

fail2ban-client ping
fail2ban-client status
fail2ban-client status sshd

De laatste uitvoer is de veelzeggende. Die toont Currently failed, Total failed, het aantal blokkades en de lijst met geblokkeerde adressen. Precies hier houden de meeste handleidingen op, en precies hier begint het probleem: een jail die netjes start en "0 banned" meldt, kan volledig blind zijn. Een jail zonder treffers ziet er identiek uit als een jail die de verkeerde logbron leest.

Drie controles maken dat verschil zichtbaar. Ten eerste: ziet het filter de regels eigenlijk wel?

fail2ban-regex systemd-journal /etc/fail2ban/filter.d/sshd.conf

Bij een bestandsgebaseerd backend in plaats daarvan:

fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf

Onderaan staat een regel als Lines: 4211 lines, 0 ignored, 137 matched, 4074 missed. Staat daar 0 matched terwijl in het log duidelijk mislukte pogingen staan, dan klopt het filter of de journalselectie niet.

Ten tweede: welk journalfilter gebruikt de jail werkelijk?

fail2ban-client get sshd journalmatch

Het antwoord verschilt per systeem, en dat verschil is belangrijk. Debian 13 meldt _SYSTEMD_UNIT=ssh.service + _COMM=sshd, Ubuntu 24.04 daarentegen _SYSTEMD_UNIT=sshd.service + _COMM=sshd. De unit heet daar dus sshd.service en op Debian ssh.service. Op Debian 12 en Ubuntu 22.04 komt in de standaardtoestand No journal match filter set terug, omdat het systemd-backend daar niet is voorgeselecteerd en de jail een logbestand leest. Dat is geen fout, maar de bevestiging dat er helemaal geen journalfilter is. Pas met backend = systemd in uw jail.local wordt er op deze systemen een van kracht.

De afzonderlijke voorwaarden worden door + gescheiden en als OF gecombineerd. _SYSTEMD_UNIT=ssh.service + _COMM=sshd past dus op alles uit de unit ssh.service of op elk proces met de naam sshd. Neem voor de tegenproef in het journal precies de unitnaam uit de uitvoer hierboven:

journalctl -u ssh --no-pager -n 50
journalctl _COMM=sshd --no-pager -n 50

Op Ubuntu 24.04 luidt de eerste regel dus journalctl -u sshd --no-pager -n 50. Wie hier de verkeerde naam pakt, ziet een lege uitvoer en zoekt daarna aan het verkeerde eind.

Duiken hier regels met Failed password for invalid user op terwijl fail2ban niets telt, dan ligt het aan de journalselectie. Dat wordt relevant zodra u OpenSSH uit backports draait of overstapt op socketactivering. Sinds OpenSSH 9.8 heet het kindproces sshd-session en niet meer sshd, en bij socketactivering heet de unit niet langer ssh.service. Vul dan in uw jail.local aan (op Ubuntu 24.04 met _SYSTEMD_UNIT=sshd.service):

[sshd]
journalmatch = _SYSTEMD_UNIT=ssh.service + _COMM=sshd + _COMM=sshd-session

Ten derde de harde proef. Blokkeer met de hand een adres uit het documentatienetwerk en kijk of het in de firewall aankomt:

fail2ban-client set sshd banip 203.0.113.10

Het commando bevestigt met 1, dus één geblokkeerd adres. Met welk gereedschap u die blokkade aantoont, hangt af van de werkzame banaction, en die staat per distributie anders ingesteld:

Systeembanaction standaardControle van de actieve blokkade
Debian 13nftablesnft list table inet f2b-table
Ubuntu 24.04nftablesnft list table inet f2b-table
Debian 12iptables-multiportiptables -n -L f2b-sshd
Ubuntu 22.04iptables-multiportiptables -n -L f2b-sshd

Het oudere Debian 11 gedraagt zich daarbij als Debian 12. Waar het verschil vandaan komt, ziet u in /etc/fail2ban/jail.d/defaults-debian.conf: alleen Debian 13 en Ubuntu 24.04 brengen daar een banaction = nftables mee, de overige blijven bij de standaardwaarde iptables-multiport uit jail.conf. Wie dat niet per server uit elkaar wil houden, zet banaction = nftables in de eigen jail.local, net als in het voorbeeld hierboven, en controleert daarna overal met nft.

Bij de nftables-actie bestaat er een tabel f2b-table in de familie inet, daarin een chain f2b-chain met prioriteit filter - 1 en een set met de naam addr-set-sshd. Daar moet 203.0.113.10 als element in staan. Een chain f2b-sshd bestaat op deze systemen niet, iptables -n -L f2b-sshd antwoordt daar met No chain/target/match by that name. Omgekeerd meldt nft list table inet f2b-table op Debian 12 en Ubuntu 22.04 Error: No such file or directory zolang daar iptables-multiport werkt. Geen van beide meldingen bewijst een kapotte configuratie, ze bewijzen alleen dat het commando niet bij de ingestelde actie past.

Bij de iptables-actie moet 203.0.113.10 dienovereenkomstig in de chain f2b-sshd staan. Pas wanneer het IP-adres in de firewall opduikt, is de keten van logregel tot verworpen pakket gesloten. Vergeet het opruimen niet.

Blokkades weer opheffen

Eén enkel adres uit een bepaalde jail losmaken:

fail2ban-client set sshd unbanip 203.0.113.10

Eén adres in één keer uit alle jails:

fail2ban-client unban 203.0.113.10

En in noodgevallen alles op nul:

fail2ban-client unban --all

Het laatste commando hebt u nodig wanneer u zichzelf hebt buitengesloten en via een noodconsole bij de server zit. Het heft alle blokkades op zonder de dienst opnieuw te starten. Komt u helemaal niet meer bij de server, dan blijft alleen de weg via de console in het klantenpaneel over. Op KVM-rootservers en dedicated servers van KernelHost bereikt u daar een VNC-sessie die los van SSH werkt. Dat is ook de reden waarom u bantime = -1 beter niet instelt op een server zonder tweede toegangsweg.

Een blokkade die u opheft, komt meteen terug wanneer de mislukte pogingen binnen het tijdvenster nog worden meegeteld. Daarom wist unban ook de bijbehorende mislukte pogingen. Wie permanent vrij moet blijven, hoort in ignoreip, gevolgd door fail2ban-client reload.

Samenspel met UFW en nftables

Hier ontstaat de schade die het moeilijkst opvalt, want fail2ban blijft vrolijk blokkades melden terwijl de pakketten er gewoon doorheen gaan.

UFW is een frontend voor iptables. Bij het starten of herladen schrijft het de filtertabel via iptables-restore opnieuw. Daarbij verdwijnen de door fail2ban aangemaakte chains zonder vervanging, en fail2ban maakt ze niet uit zichzelf opnieuw aan. Na elke ufw enable, ufw disable of ufw reload geldt daarom: systemctl restart fail2ban. Tot dat moment staat in fail2ban-client status sshd een lange lijst geblokkeerde IP-adressen waarvan er geen enkel werkelijk wordt tegengehouden.

Schoner is het om fail2ban rechtstreeks via UFW te laten blokkeren. De actie staat in /etc/fail2ban/action.d/ufw.conf en zit in alle vier de distributies. In jail.local:

[DEFAULT]
banaction = ufw
banaction_allports = ufw

Dan belanden de blokkades als regels in UFW zelf en overleven ze het herladen daarvan. Controle:

ufw status numbered

Geblokkeerde adressen verschijnen daar als DENY IN ver bovenaan de lijst. Belangrijk is dat de blokkeerregels vóór uw vrijgave voor poort 22 staan, anders gaat de vrijgave voor. De meegeleverde actie zet ze daarom met insert vooraan.

Wie geen UFW gebruikt en in plaats daarvan een eigen regelwerk in /etc/nftables.conf onderhoudt, heeft een verwant probleem. Een flush ruleset aan het begin van dat bestand, precies zoals het standaardsjabloon voorstelt, wist bij het herladen ook f2b-table. Ofwel u ziet af van de globale flush en leegt alleen uw eigen tabel, ofwel u koppelt de herstart van fail2ban aan de nftables-dienst. Het voordeel van de eigen tabel van fail2ban is dat die met prioriteit filter - 1 vóór de normale filtertabel aan de beurt komt en daarmee ook vóór regels uit iptables-nft.

Wat u niet moet mengen: banaction = nftables met een eigen iptables-regelwerk, in de verwachting dat iptables -L de blokkades toont. Beide wegen werken parallel, maar elk gereedschap toont alleen zijn eigen regelwerk. Controleer altijd met het gereedschap dat bij de ingestelde banaction hoort.

Foutmeldingen letterlijk, en wat erachter zit

De logs vindt u afhankelijk van het systeem op twee plaatsen:

journalctl -u fail2ban --no-pager -n 100
tail -n 100 /var/log/fail2ban.log

Failed during configuration: Have not found any log file for sshd jail

De kenmerkende melding van Debian 12. De jail draait met backend = auto, zoekt /var/log/auth.log, en dat bestand bestaat niet omdat er geen rsyslog geïnstalleerd is en alles in het journal belandt. De dienst start dan helemaal niet, systemctl status fail2ban toont Failed with result 'exit-code'. Twee oplossingen: ofwel apt install -y rsyslog en één keer herstarten, ofwel in jail.local onder [sshd] de regel backend = systemd zetten. De tweede weg is de modernere, maar die vraagt om het volgende pakket.

Backend 'systemd' failed to initialize due to No module named 'systemd'

U hebt backend = systemd gezet, maar de Python-koppeling met het journal ontbreekt. Op Debian 12 is python3-systemd alleen aanbevolen en wordt het bij installaties zonder aanbevolen pakketten niet meegenomen. Oplossing:

apt install -y python3-systemd
systemctl restart fail2ban

Failed to access socket path: /var/run/fail2ban/fail2ban.sock. Is fail2ban running?

De client bereikt de server niet. In verreweg de meeste gevallen draait de dienst simpelweg niet, omdat hij op een configuratiefout is stukgelopen. Lees eerst systemctl status fail2ban, daarna het journal. Zelden blijft er na een harde afbreking een verweesd socketbestand achter, dan meldt het starten van de server Server already running. Verwijder in dat geval het socketbestand en start opnieuw.

NOK: ('sshd',)

Het antwoord op fail2ban-client status sshd wanneer deze jail tijdens de looptijd niet bestaat. Ofwel ontbreekt enabled = true, ofwel hebt u de naam verkeerd getypt, ofwel heeft een syntaxfout hoger in jail.local de sectie opgeslokt. Welke jails werkelijk draaien, toont fail2ban-client status. Wat fail2ban uit alle bestanden heeft samengesteld, toont de configuratie-uitdraai:

fail2ban-client -d

Error banning en iptables not found

De jail telt correct, maar de blokkade struikelt over de actie. Typisch op kale Debian-12-installaties waarop alleen nftables aanwezig is, terwijl de standaardwaarde iptables-multiport luidt. Installeer alsnog met apt install -y iptables, of stap in jail.local over op banaction = nftables. De tweede weg is precies de weg die Debian 13 en Ubuntu 24.04 uit zichzelf al gaan.

Als niets meer helpt: netjes terugdraaien

Omdat alle aanpassingen in jail.local staan, is de weg terug kort. Bestand aan de kant zetten, dienst herstarten, en u zit weer op de instellingen van het pakket:

mv /etc/fail2ban/jail.local /root/jail.local.bak
systemctl restart fail2ban
fail2ban-client status

Draait het zo wel, dan zit de fout in uw configuratie en voegt u de blokken stuk voor stuk weer toe. Draait het ook zo niet, dan ligt het aan de omgeving, dus aan de ontbrekende logbron of aan het ontbrekende firewallgereedschap. Precies daarom blijft jail.conf onaangeroerd: dit terugdraaien in tien seconden lukt alleen zolang het pakketbestand in de standaardtoestand is.

Tot slot de plaatsbepaling. fail2ban vermindert de ruis in de logs en stopt trage, herhaalde inlogpogingen. Een serieuze aanval uit een groot botnet gebruikt elk adres maar één keer en loopt langs elke vorm van rate limiting heen. De effectievere stap tegen brute force op SSH blijft het uitschakelen van het inloggen met wachtwoord ten gunste van sleutels. En tegen volumetrische aanvallen helpt sowieso alleen filtering in het netwerk, bij ons via 3,2 Tbps Arbor-realtimefiltering rechtstreeks in het datacenter in Frankfurt am Main en tot 17 Tbps globale filtercapaciteit in de Professional-tarieven. fail2ban is de laag daaronder, op de host, en daar doet het zijn werk betrouwbaar zodra de drie dingen kloppen: de juiste logbron, de juiste firewallactie en een jail.local die u desnoods in één keer weer kunt verwijderen.

Veelgestelde vragen

Moet ik jail.conf echt onaangeroerd laten?
Ja. Het bestand is eigendom van het pakket en kan bij updates worden vervangen. Alle aanpassingen horen in /etc/fail2ban/jail.local. Dat bestand wordt na jail.conf en na jail.d/*.conf gelezen en overschrijft daarmee ook de instellingen uit defaults-debian.conf.
Waarom start fail2ban op Debian 12 na de installatie niet?
Omdat de sshd-jail met backend = auto werkt en /var/log/auth.log verwacht, dat zonder geïnstalleerde rsyslog niet bestaat. In het journal staat dan "Failed during configuration: Have not found any log file for sshd jail". Oplossing: ofwel rsyslog installeren, ofwel backend = systemd zetten en python3-systemd alsnog installeren.
Wat betekent backend = auto precies?
auto probeert achtereenvolgens pyinotify en polling. Beide zijn bestandsgebaseerde methoden. auto kiest nooit het systemd-journal. Wie het journal wil uitlezen, moet backend = systemd uitdrukkelijk instellen.
Hoe hef ik een blokkade weer op?
Afzonderlijk met fail2ban-client set sshd unbanip IP-adres, over alle jails heen met fail2ban-client unban IP-adres, en in noodgevallen alles in één keer met fail2ban-client unban --all. Wie permanent vrij moet blijven, hoort in ignoreip, gevolgd door fail2ban-client reload.
Waarom blokkeert fail2ban wel, maar komt de aanvaller er toch doorheen?
Meestal heeft UFW intussen de filtertabel via iptables-restore opnieuw geschreven en daarbij de fail2ban-chains verwijderd. Na elke ufw enable, disable of reload moet fail2ban opnieuw worden gestart. Schoner is banaction = ufw, dan staan de blokkades in UFW zelf.
Waaraan zie ik dat fail2ban werkelijk werkt?
Niet aan het feit dat de dienst draait. Controleer met fail2ban-regex of het filter regels raakt, en blokkeer bij wijze van test met de hand een adres uit 203.0.113.0/24. Daarna moet het in de firewall opduiken, passend bij de ingestelde banaction: op Debian 13 en Ubuntu 24.04 met nft list table inet f2b-table in de set addr-set-sshd, op Debian 12 en Ubuntu 22.04 met iptables -n -L f2b-sshd in de gelijknamige chain. Een chain f2b-sshd bestaat op nftables-systemen niet.
Vervangt fail2ban een DDoS-bescherming?
Nee. fail2ban werkt op de host tegen herhaalde inlogpogingen van afzonderlijke adressen. Volumetrische aanvallen moeten in het netwerk worden gefilterd, voordat ze de server bereiken.

fail2ban SSH Serverbeveiliging Brute force Debian Ubuntu nftables UFW