<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Fail2ban – Serverküche</title><link>https://serverkueche.de/en/tags/fail2ban/</link><description>Fail2ban – Neueste Beiträge von Serverküche</description><generator>Hugo</generator><language>en-US</language><managingEditor>feedback@serverkueche.de (Serverküche)</managingEditor><webMaster>feedback@serverkueche.de (Serverküche)</webMaster><copyright>2026 Serverküche</copyright><lastBuildDate>Sat, 18 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://serverkueche.de/en/tags/fail2ban/index.xml" rel="self" type="application/rss+xml"/><item><title>Setting up Fail2ban: block brute-force attacks automatically</title><link>https://serverkueche.de/en/tutorials/fail2ban-setup/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><author>feedback@serverkueche.de (Serverküche)</author><guid>https://serverkueche.de/en/tutorials/fail2ban-setup/</guid><description>Fail2ban watches your logs and bans IPs after too many failed attempts – with a safe whitelist for your own address so you don't lock yourself out.</description><content:encoded><![CDATA[<p>After a few days on the internet, take a look at <code>journalctl -u ssh</code>: hundreds of
login attempts from foreign IPs, one every second. As long as your SSH is
<a href="/en/tutorials/harden-ssh/">switched to key login</a>, none of them gets in – but it
clutters the log and eats resources. <strong>Fail2ban</strong> reads along with these logs and
<strong>bans automatically</strong> whoever tries too often without success.</p>
<h2 id="what-are-we-building">What are we building?</h2>
<p>By the end, <strong>Fail2ban 1.1</strong> on your <strong>Debian 13</strong> monitors the SSH logins and, after
too many failed attempts, bans the attacking IP for a defined time – via a firewall
rule. <strong>Your own IP is on a whitelist</strong> so this never hits you. You can view bans, set
them manually and lift them again.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>A <a href="/en/tutorials/harden-ssh/">hardened server</a> (key login) with an
<a href="/en/tutorials/firewall-ufw-setup/">active firewall</a></li>
<li>Your <strong>own public IP</strong> – find it e.g. via
<a href="https://www.wieistmeineip.de">wieistmeineip.de</a> or <code>curl -s ifconfig.me</code> from your
local machine</li>
</ul>
<h2 id="step-by-step">Step by step</h2>
<p>Before we configure, three terms that explain the whole tool:</p>
<ul>
<li><strong>Filter</strong> – a pattern that detects a &ldquo;failed login&rdquo; in the log (for SSH, Fail2ban
brings this ready-made).</li>
<li><strong>Jail</strong> – connects a filter with a log source and the rules &ldquo;how often in what time
frame&rdquo;. The <code>sshd</code> jail monitors the SSH logins.</li>
<li><strong>Action</strong> – what happens when it&rsquo;s exceeded: by default a <strong>firewall rule</strong> that
blocks the IP for <code>bantime</code>.</li>
</ul>
<p>In short: filter detects failed attempts → jail counts them → action bans. Everything
you configure below is one of these three knobs.</p>
<h3 id="step-1-install-fail2ban">Step 1: Install Fail2ban</h3>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo apt update
</span></span><span class="line"><span class="cl">sudo apt install -y fail2ban</span></span></code></pre></div>
</div>
<p>Check the version:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">fail2ban-client --version</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Fail2Ban v1.1.0</span></span></code></pre></div>
</div>
<h3 id="step-2-your-own-configuration-in-jaillocal">Step 2: Your own configuration in jail.local</h3>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>Never edit jail.conf
  </p>
  <div class="prose-kitchen text-sm">The shipped <code>/etc/fail2ban/jail.conf</code> is overwritten on updates. Your own settings
<strong>always</strong> belong in <code>/etc/fail2ban/jail.local</code> – this file is preserved and overrides
the defaults.</div>
</div>
<p>Create the file:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo nano /etc/fail2ban/jail.local</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">INI</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[DEFAULT]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># How long the ban lasts</span>
</span></span><span class="line"><span class="cl"><span class="na">bantime</span> <span class="o">=</span> <span class="s">1h</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Time window in which the failed attempts count</span>
</span></span><span class="line"><span class="cl"><span class="na">findtime</span> <span class="o">=</span> <span class="s">10m</span>
</span></span><span class="line"><span class="cl"><span class="c1"># This many failed attempts are allowed, then a ban</span>
</span></span><span class="line"><span class="cl"><span class="na">maxretry</span> <span class="o">=</span> <span class="s">5</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Your own IP(s) – will NEVER be banned</span>
</span></span><span class="line"><span class="cl"><span class="na">ignoreip</span> <span class="o">=</span> <span class="s">127.0.0.1/8 ::1 YOUR_OWN_IP</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[sshd]</span>
</span></span><span class="line"><span class="cl"><span class="na">enabled</span> <span class="o">=</span> <span class="s">true</span></span></span></code></pre></div>
</div>
<p>Line by line:</p>
<ul>
<li><strong><code>bantime = 1h</code></strong> – ban duration. For stubborn cases, see the escalation in step 5.</li>
<li><strong><code>findtime</code> + <code>maxretry</code></strong> – &ldquo;5 failed attempts within 10 minutes → ban&rdquo;.</li>
<li><strong><code>ignoreip</code></strong> – the most important line: enter <strong>your own IP</strong> here so you don&rsquo;t lock
yourself out. If you have a changing IP at home, better use access via a fixed point
(a VPN later) instead of a broad allowance.</li>
<li><strong><code>[sshd] enabled = true</code></strong> – activates the SSH jail.</li>
</ul>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>Enter your own IP first
  </p>
  <div class="prose-kitchen text-sm">Set your own IP in <code>ignoreip</code> <strong>before</strong> you start Fail2ban – otherwise even your own
typo on login could lock you out. The address you&rsquo;re currently connected from is shown
by the first field of <code>echo &quot;$SSH_CLIENT&quot;</code>.</div>
</div>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-herb-400 bg-herb-50 dark:border-herb-700 dark:bg-herb-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🧑‍🍳</span>Debian 13 reads the systemd journal
  </p>
  <div class="prose-kitchen text-sm">On Debian 13, Fail2ban uses the <strong>systemd journal</strong> as its source by default – you
don&rsquo;t need to specify a <code>logpath</code> (like <code>/var/log/auth.log</code>). On modern systems that
file often no longer exists at all; the journal is the right source and works without
extra configuration.</div>
</div>
<h3 id="step-3-start-and-enable-the-service">Step 3: Start and enable the service</h3>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo systemctl <span class="nb">enable</span> --now fail2ban</span></span></code></pre></div>
</div>
<p><code>enable --now</code> starts the service immediately <strong>and</strong> ensures it runs again
automatically after a reboot. Check that it runs cleanly:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo systemctl status fail2ban</span></span></code></pre></div>
</div>
<p>You should see <code>active (running)</code>. After every change to <code>jail.local</code>, reload the
configuration with <code>sudo systemctl reload fail2ban</code>.</p>
<h3 id="step-4-check-the-status">Step 4: Check the status</h3>
<p>This is how you see which jails are active:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo fail2ban-client status</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Status
</span></span><span class="line"><span class="cl">|- Number of jail:	1
</span></span><span class="line"><span class="cl">`- Jail list:	sshd</span></span></code></pre></div>
</div>
<p>And the details of the SSH jail:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo fail2ban-client status sshd</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Status for the jail: sshd
</span></span><span class="line"><span class="cl">|- Filter
</span></span><span class="line"><span class="cl">|  |- Currently failed:	0
</span></span><span class="line"><span class="cl">|  |- Total failed:	0
</span></span><span class="line"><span class="cl">|  `- Journal matches:	_SYSTEMD_UNIT=ssh.service + _COMM=sshd
</span></span><span class="line"><span class="cl">`- Actions
</span></span><span class="line"><span class="cl">   |- Currently banned:	0
</span></span><span class="line"><span class="cl">   |- Total banned:	0
</span></span><span class="line"><span class="cl">   `- Banned IP list:</span></span></code></pre></div>
</div>
<p>The <code>Journal matches</code> line confirms that Fail2ban reads the systemd journal.
<code>Currently banned</code> rises as soon as someone gets it wrong too often.</p>
<h3 id="step-5-manage-bans--and-escalate-them">Step 5: Manage bans – and escalate them</h3>
<p>Manually unban an IP (e.g. when a colleague mistyped):</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo fail2ban-client <span class="nb">set</span> sshd unbanip 203.0.113.45</span></span></code></pre></div>
</div>
<p>Ban an IP immediately:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo fail2ban-client <span class="nb">set</span> sshd banip 203.0.113.45</span></span></code></pre></div>
</div>
<p>Against especially stubborn attackers, an <strong>escalating ban time</strong> pays off: whoever
comes back is banned for longer. Add to the <code>[DEFAULT]</code> block:</p>
<div class="sk-code">
  <span class="sk-code-head">INI</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">bantime.increment</span> <span class="o">=</span> <span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">bantime.maxtime</span> <span class="o">=</span> <span class="s">1w</span></span></span></code></pre></div>
</div>
<p>With that, Fail2ban doubles the ban time each time the same IP reoffends – up to a
maximum of one week. After the change, <code>sudo systemctl reload fail2ban</code>.</p>
<h2 id="when-things-go-wrong">When things go wrong</h2>
<div class="troubleshoot not-prose">
<p><strong>You locked yourself out.</strong> <code>ignoreip</code> was missing or contained the wrong IP. Connect
via the <strong>console in the netcup SCP</strong> (independent of SSH) and unban yourself with
<code>sudo fail2ban-client set sshd unbanip YOUR_OWN_IP</code>. Then enter your IP in <code>ignoreip</code>
and reload. That&rsquo;s why the whitelist comes first in step 2.</p>
<p><strong><code>fail2ban.service</code> won&rsquo;t start (<code>systemctl status</code> shows &ldquo;failed&rdquo;).</strong> Almost always
a typo in <code>jail.local</code>. Check the syntax with <code>sudo fail2ban-client -t</code> (test mode) –
the command names the faulty line.</p>
<p><strong>No one is ever banned, even though the log is full of failed attempts.</strong> Check with
<code>sudo fail2ban-client status sshd</code> whether <code>Total failed</code> rises at all. If it stays at
0, the filter isn&rsquo;t finding the entries – usually because an outdated guide set a
<code>logpath</code> to a non-existent file. On Debian 13, <strong>remove</strong> the <code>logpath</code> from
<code>jail.local</code> and use the journal (step 2).</p>
<p><strong>Bans &ldquo;don&rsquo;t work&rdquo; – the IP keeps connecting.</strong> Fail2ban writes its block rules into
the same firewall (nftables/iptables) as UFW. Check with <code>sudo nft list ruleset | grep f2b</code> (or <code>sudo iptables -L -n | grep f2b</code>) whether the <code>f2b</code> chains exist. If they&rsquo;re
missing, the interplay with the firewall is stuck – restart the service and look at
the logs.</p>

</div>

<h2 id="maintenance--backups">Maintenance &amp; backups</h2>
<ul>
<li><strong>Keep an eye on bans:</strong> <code>sudo fail2ban-client status sshd</code> shows at any time how
many IPs are currently banned. A look at the journal (<code>journalctl -u fail2ban</code>) shows
the history.</li>
<li><strong>More jails:</strong> as soon as services with their own login are added (e.g. a web app),
you can activate matching jails – following the same pattern as <code>[sshd]</code>.</li>
<li><strong>Back up <code>jail.local</code>:</strong> your configuration is quickly restored but belongs in the
backup of your server configuration.</li>
<li><strong>Outlook CrowdSec:</strong> Fail2ban protects your host based on <em>your</em> logs. The modern
successor <strong>CrowdSec</strong> later adds <em>collaborative</em> intrusion prevention (shared block
lists) to this – we build it in a dedicated recipe once the reverse proxy is up. To
get started, Fail2ban is exactly right.</li>
</ul>
<p>With that, the security foundation is complete: key login, two firewall layers
(<a href="/en/tutorials/firewall-ufw-setup/">UFW</a> +
<a href="/en/tutorials/netcup-firewall-setup/">netcup firewall</a>),
<a href="/en/tutorials/unattended-upgrades-automatic-updates/">automatic updates</a> and active
brute-force protection. Your server is now not just set up, but solidly secured.</p>
]]></content:encoded></item></channel></rss>