<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: fcalculator</title>
    <description>The latest articles on DEV Community by fcalculator (@fcalculator).</description>
    <link>https://dev.to/fcalculator</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4070601%2F484e9282-35c1-4bbb-b7d8-325d79385224.png</url>
      <title>DEV Community: fcalculator</title>
      <link>https://dev.to/fcalculator</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fcalculator"/>
    <language>en</language>
    <item>
      <title>Make fail2ban report its bans to a community blacklist</title>
      <dc:creator>fcalculator</dc:creator>
      <pubDate>Mon, 10 Aug 2026 06:05:32 +0000</pubDate>
      <link>https://dev.to/fcalculator/make-fail2ban-report-its-bans-to-a-community-blacklist-288g</link>
      <guid>https://dev.to/fcalculator/make-fail2ban-report-its-bans-to-a-community-blacklist-288g</guid>
      <description>&lt;p&gt;Check the auth log on any server with port 22 open and it's always the same story: endless login attempts from IPs that have clearly done this before. fail2ban bans them, the bantime runs out, and that's it. Nothing learned, nothing shared.&lt;/p&gt;

&lt;p&gt;A while ago I started sending those bans to a shared blacklist instead of letting them disappear. I use the one on bl.ipwhois.net, mostly because it needs no signup and no API key. You get 500 requests a day per source IP, which is plenty for a normal server.&lt;/p&gt;

&lt;p&gt;Checking an address looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://bl.ipwhois.net/api/check?ip=203.0.113.5"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"203.0.113.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"listed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Not found in IPWhois Blacklist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"rate_limit"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"remaining"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;499&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reporting is a POST to the same API. To make fail2ban do it on every ban, drop this into &lt;code&gt;/etc/fail2ban/action.d/ipwhois-report.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Definition]&lt;/span&gt;
&lt;span class="py"&gt;actionstart&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
&lt;span class="py"&gt;actionstop&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
&lt;span class="py"&gt;actioncheck&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
&lt;span class="py"&gt;actionban&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;curl -s -X POST "https://bl.ipwhois.net/api/report" -d "ip=&amp;lt;ip&amp;gt;" -d "type=&amp;lt;name&amp;gt;" -d "comment=fail2ban &amp;lt;name&amp;gt; ban"&lt;/span&gt;
&lt;span class="py"&gt;actionunban&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and add the action to whatever jails you want in &lt;code&gt;jail.local&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[sshd]&lt;/span&gt;
&lt;span class="py"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;iptables-multiport[name=sshd, port="22"]&lt;/span&gt;
         &lt;span class="err"&gt;ipwhois-report&lt;/span&gt;&lt;span class="nn"&gt;[name=sshd]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reload fail2ban and that's it. The &lt;code&gt;&amp;lt;ip&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;name&amp;gt;&lt;/code&gt; tags get filled in by fail2ban itself, so the jail name ends up as the threat type.&lt;/p&gt;

&lt;p&gt;The other direction is more useful. There's a plain text feed of everything the community reported (only direct reports, fail2ban setups like this one, and honeypots, they don't mix in other people's feeds). One IPv4 per line, refreshed every 30 minutes. I load it into an ipset once an hour:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://bl.ipwhois.net/feed.txt | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s1"&gt;'^#'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/ipwhois_bl.txt
ipset create ipwhois_bl &lt;span class="nb"&gt;hash&lt;/span&gt;:ip &lt;span class="nt"&gt;-exist&lt;/span&gt;
ipset flush ipwhois_bl
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; ip&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;ipset add ipwhois_bl &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ip&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-exist&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt; &amp;lt; /tmp/ipwhois_bl.txt
iptables &lt;span class="nt"&gt;-I&lt;/span&gt; INPUT &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--match-set&lt;/span&gt; ipwhois_bl src &lt;span class="nt"&gt;-j&lt;/span&gt; DROP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's around a thousand entries, the whole refresh takes under a second.&lt;/p&gt;

&lt;p&gt;Two warnings from experience. Whitelist your own IPs above the DROP rule, because sooner or later you'll be connecting from a network somebody reported. And run the rule with &lt;code&gt;-j LOG&lt;/code&gt; instead of &lt;code&gt;DROP&lt;/code&gt; for a day first, just to see what it would have caught.&lt;/p&gt;

&lt;p&gt;One more habit that stuck: when a banned IP keeps coming back, I run it through an &lt;a href="https://ipwhois.net/ip-lookup" rel="noopener noreferrer"&gt;IP lookup&lt;/a&gt; to see who actually owns it. Half the time it's some cheap VPS provider, and a quick &lt;a href="https://ipwhois.net" rel="noopener noreferrer"&gt;IP whois&lt;/a&gt; check turns up the abuse contact. Forwarding a chunk of auth.log to that address works more often than you'd think, especially with smaller hosters.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>security</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
