{"id":1517,"date":"2026-10-26T22:12:09","date_gmt":"2026-10-26T22:12:09","guid":{"rendered":"https:\/\/virtualserversvps.com\/blog\/?p=1517"},"modified":"2026-10-26T22:12:09","modified_gmt":"2026-10-26T22:12:09","slug":"fail2ban-jail-regex-tarpit-single-vcpu-cost","status":"publish","type":"post","link":"https:\/\/virtualserversvps.com\/blog\/fail2ban-jail-regex-tarpit-single-vcpu-cost\/","title":{"rendered":"Fail2Ban on a Busy VPS: Jail Regex Cost, Tarpit Behaviour and CPU You Didn&#8217;t Budget For"},"content":{"rendered":"<div style=\"padding:20px\">\n<p>Fail2Ban installations are cheap to add and expensive to keep cheap. The package itself idles at a few megabytes, but each jail you enable spawns a log reader with a regex, and on a single-vCPU VPS a badly written filter can consume more CPU than the service it is protecting. This article is about the running cost of an existing fail2ban setup: which jails earn their cycles, why broad regexes blow up, how ban churn affects the kernel firewall, and where to verify with <code>top<\/code> and <code>pidstat<\/code> before you add another filter. If you&#8217;re still picking hardware, the <a href=\"https:\/\/virtualserversvps.com\/\">VPS plans sized for small daemons like fail2ban<\/a> matter here \u2014 one vCPU makes every percentage point visible.<\/p>\n<h2>Where the CPU Actually Goes<\/h2>\n<p>Fail2ban&#8217;s architecture is one thread per jail (by default). Each thread tails its logfile, runs the filter&#8217;s failregex line by line, and on a match calls into the ban action \u2014 usually <code>iptables<\/code> or <code>nftables<\/code>. Three costs follow from that: parsing volume (lines per second \u00d7 regex cost), match rate (how often a ban fires), and action cost (how expensive the ban\/unban syscall path is). On a quiet VPS the first dominates during log bursts; the third dominates during an attack.<\/p>\n<pre><code>systemctl status fail2ban --no-pager\nfail2ban-client status\nps -o pid,pcpu,pmem,etime,cmd -C fail2ban-server\npidstat -t -p $(pgrep -f fail2ban-server) 5 12<\/code><\/pre>\n<p><code>pidstat -t<\/code> breaks the number out per thread, which is the only way to see which jail is burning cycles. The main thread stays near zero; a jail thread at 8% of a single core is one eighth of your entire machine.<\/p>\n<h2>Log Parsing Cost per Jail<\/h2>\n<p>Measure it directly instead of guessing. <code>fail2ban-regex<\/code> runs your filter against a sample log and prints timing and match counts:<\/p>\n<pre><code>fail2ban-regex \/var\/log\/auth.log \/etc\/fail2ban\/filter.d\/sshd.conf --print-all-matched<\/code><\/pre>\n<p>Expected output shape on a 50 MB auth log:<\/p>\n<pre><code>Lines: 214833 lines, 0 ignored, 1842 matched, 212991 missed\nDate template hits: 214833\nSuccess, the total number of match is 1842\n\nPerformance:\n============\n  Hits: 1842  [.. ]\n  Misses: 212991\n  ...\n  Total: 3.812 s   (56,350 lines\/s)<\/code><\/pre>\n<p>56k lines\/sec on a 1 vCPU box sounds fine until you note that auth.log during an SSH brute-force campaign can grow at 2,000 lines\/sec across a handful of IPs. That&#8217;s 3.5% of the core just to read the file \u2014 acceptable. Now try a filter with <code>.*<\/code> in the middle of the regex against the same log and the throughput drops by an order of magnitude. The fix is narrowing the regex to the literal prefix the daemon actually emits.<\/p>\n<h2>How Broad Regexes Blow Up<\/h2>\n<p>Fail2ban&#8217;s filter regexes are Python <code>re<\/code> patterns matched against each line, anchored at the start by default. The catastrophic patterns are the ones with nested quantifiers or a wildcard prefix:<\/p>\n<pre><code># Bad: matches almost every line, then fails at the end\nfailregex = ^.*Failed password.* from &lt;HOST&gt;\n\n# Better: anchor to the literal the daemon emits first\nfailregex = ^Failed password for (?:invalid user )?\\S+ from &lt;HOST&gt; port \\d+ ssh2$\n\n# For journald-backed units, filter at the source\nbackend = systemd\njournalmatch = _SYSTEMD_UNIT=sshd.service<\/code><\/pre>\n<p>The bad line does not just match more \u2014 it forces the regex engine to scan the whole line before failing, on every line, including the 200,000 that will never match. The better form fails in the first token. On a busy auth log that is the difference between 2% and 20% of a core. Add <code>journalmatch<\/code> and the kernel hands you only the relevant records, removing most of the parse cost entirely.<\/p>\n<h2>Ban Churn: The iptables Cost Nobody Budgets<\/h2>\n<p>Every ban and unban writes a rule. With default <code>iptables<\/code> and a linear ruleset, each insert is O(n) in the number of existing rules, and a distributed SSH attack can produce hundreds of rules in minutes. The visible symptom is not fail2ban CPU \u2014 it&#8217;s <code>ksoftirqd<\/code> and packet processing time on the forwarding path.<\/p>\n<pre><code>iptables -L f2b-sshd -n | wc -l\nnft list set inet f2b-table addr-set-sshd 2&gt;\/dev\/null | head\nipset list f2b-sshd 2&gt;\/dev\/null | grep -c '^[0-9]'\n\nwatch -n2 'iptables -L -n | wc -l'<\/code><\/pre>\n<p>If the rule count climbs past a few thousand, switch the action to an ipset or nftables set:<\/p>\n<pre><code># \/etc\/fail2ban\/jail.d\/defaults-debian.conf\n[DEFAULT]\nbanaction = nftables-multiport\nbanaction_allports = nftables-allports<\/code><\/pre>\n<p>Set membership is O(1) per packet versus a linear walk, and adding a ban no longer rewrites a chain. On a single vCPU serving HTTP, this change is usually worth more than any filter tuning.<\/p>\n<h2>Why sshd Plus recidive Is Usually Enough<\/h2>\n<p>The default Debian\/Ubuntu install ships a dozen jails: <code>sshd<\/code>, <code>apache-auth<\/code>, <code>nginx-http-auth<\/code>, <code>postfix<\/code>, <code>proftpd<\/code>, <code>vsftpd<\/code> and more. Most of them tail logfiles that do not exist on your box, which is harmless but wasteful \u2014 and a few of them, on a shared log, duplicate work. The pattern that covers real attacks with minimal cost:<\/p>\n<pre><code>[sshd]\nenabled  = true\nmaxretry = 4\nfindtime = 10m\nbantime  = 1h\nbackend  = systemd\n\n[recidive]\nenabled  = true\nlogpath  = \/var\/log\/fail2ban.log\nbantime  = 1w\nfindtime = 1d\nmaxretry = 3<\/code><\/pre>\n<p>sshd catches the first attempt; recidive escalates anyone who comes back after three bans to a week. That two-jail setup handles the overwhelming majority of automated SSH traffic. Add <code>nginx-limit-req<\/code> only if you actually see request-rate abuse in your access log, and verify with <code>fail2ban-regex<\/code> that it matches before enabling it \u2014 a jail that matches nothing is pure cost.<\/p>\n<p>A common mistake is setting <code>maxretry = 1<\/code> with a 1-hour bantime. That produces ban churn from legitimate users with a stale key, fills the ban list with real humans, and hides actual attackers among them. Four retries in ten minutes is enough to stop scripts and forgiving enough to keep people out of the ban list.<\/p>\n<h2>Tarpit and Blackhole: Behaviour and Downside<\/h2>\n<p>Adding <code>iptables -j TARPIT<\/code> (from xtables-addons) or <code>-j DROP<\/code> changes the failure mode. <code>DROP<\/code> is silent: the attacker&#8217;s SYN goes nowhere, they retry, and their timeout is minutes. <code>TARPIT<\/code> accepts the connection and then holds it open with a zero window, which keeps the attacker&#8217;s socket occupied \u2014 but each held socket consumes kernel memory and a conntrack entry on your box.<\/p>\n<pre><code>conntrack -C\ncat \/proc\/sys\/net\/netfilter\/nf_conntrack_count\ncat \/proc\/sys\/net\/netfilter\/nf_conntrack_max<\/code><\/pre>\n<p>If <code>nf_conntrack_count<\/code> approaches 80% of <code>max<\/code>, you get <code>nf_conntrack: table full, dropping packet<\/code> in dmesg and legitimate connections start failing. A tarpit under a 10k-connection flood will fill the table on a 2 GB VPS long before the attacker gives up. For most single-server setups, <code>REJECT --reject-with tcp-reset<\/code> is the better trade: it ends the connection immediately, costs nothing to hold, and the attacker&#8217;s script moves on. Save the tarpit for hosts with conntrack headroom and a reason to waste attacker time.<\/p>\n<h2>Reading status Output Correctly<\/h2>\n<pre><code>fail2ban-client status sshd<\/code><\/pre>\n<pre><code>Status for the jail: sshd\n|- Filter\n|  |- Currently failed: 7\n|  |- Total failed:     4183\n|  `- Journal matches:  _SYSTEMD_UNIT=sshd.service + _COMM=sshd\n`- Actions\n   |- Currently banned: 112\n   |- Total banned:     967\n   `- Banned IP list:   45.148.x.x 103.79.x.x ...<\/code><\/pre>\n<p>Two ratios matter. <code>Total banned<\/code> growing steadily is normal. <code>Currently banned<\/code> close to <code>Total banned<\/code> means bantime is long relative to attack volume and the list is growing without bound \u2014 check whether those IPs ever return. And if <code>Total failed<\/code> is climbing by thousands an hour while <code>Total banned<\/code> barely moves, your <code>findtime<\/code> is too short or <code>maxretry<\/code> too high for the actual pattern.<\/p>\n<h2>Tuning Checklist<\/h2>\n<ul>\n<li><strong>Backend:<\/strong> <code>systemd<\/code> for anything journald logs; skips file parsing entirely.<\/li>\n<li><strong>Jails:<\/strong> sshd + recidive. Add one more only with a verified match rate.<\/li>\n<li><strong>Action:<\/strong> nftables or ipset once rule counts exceed a few hundred.<\/li>\n<li><strong>findtime:<\/strong> 10 minutes for SSH; long enough to catch a scripted retry, short enough to avoid banning humans.<\/li>\n<li><strong>bantime:<\/strong> 1 hour first tier, 1 week for recidive. Incremental <code>bantime.increment = true<\/code> with <code>bantime.factor = 2<\/code> works well.<\/li>\n<li><strong>ignoreip:<\/strong> your own admin IPs and monitoring, or you will lock yourself out during an upgrade.<\/li>\n<\/ul>\n<p>Cost, not features, is the constraint on a single vCPU. Two jails, a journald backend and a set-based ban action will sit under 1% of a core while stopping the same traffic that a dozen file-tailing jails handle at ten times the cost. Measure before and after with <code>pidstat -t<\/code> and keep the numbers \u2014 if the <a href=\"https:\/\/virtualserversvps.com\/\">VPS you&#8217;re on<\/a> shows the jail thread above 5% of a core at idle, something in the filter or the action is wrong.<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Fail2Ban installations are cheap to add and expensive to keep cheap. The package itself idles at a few megabytes, but each jail you enable spawns a log reader with a&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":0,"footnotes":""},"categories":[4],"tags":[],"class_list":["post-1517","post","type-post","status-publish","format-standard","hentry","category-security-compliance"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v26.1 (Yoast SEO v26.1) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Fail2Ban on a Busy VPS: Jail Regex Cost, Tarpit Behaviour and CPU You Didn&#039;t Budget For - Virtual Servers VPS Blog<\/title>\n<meta name=\"description\" content=\"Fail2Ban installations are cheap to add and expensive to keep cheap. The package itself idles at a few megabytes, but each jail you enable spawns a log\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/virtualserversvps.com\/blog\/fail2ban-jail-regex-tarpit-single-vcpu-cost\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Fail2Ban on a Busy VPS: Jail Regex Cost, Tarpit Behaviour and CPU You Didn&#039;t Budget For\" \/>\n<meta property=\"og:description\" content=\"Fail2Ban on a Busy VPS: Jail Regex Cost, Tarpit Behaviour and CPU You Didn&#039;t Budget For\" \/>\n<meta property=\"og:url\" content=\"https:\/\/virtualserversvps.com\/blog\/fail2ban-jail-regex-tarpit-single-vcpu-cost\/\" \/>\n<meta property=\"og:site_name\" content=\"Virtual Servers VPS Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-10-26T22:12:09+00:00\" \/>\n<meta name=\"author\" content=\"Virtual-Servers-Vps-Editor\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Virtual-Servers-Vps-Editor\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"7 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/fail2ban-jail-regex-tarpit-single-vcpu-cost\/\",\"url\":\"https:\/\/virtualserversvps.com\/blog\/fail2ban-jail-regex-tarpit-single-vcpu-cost\/\",\"name\":\"Fail2Ban on a Busy VPS: Jail Regex Cost, Tarpit Behaviour and CPU You Didn't Budget For - Virtual Servers VPS Blog\",\"isPartOf\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#website\"},\"datePublished\":\"2026-10-26T22:12:09+00:00\",\"author\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0\"},\"description\":\"Fail2Ban installations are cheap to add and expensive to keep cheap. The package itself idles at a few megabytes, but each jail you enable spawns a log\",\"breadcrumb\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/fail2ban-jail-regex-tarpit-single-vcpu-cost\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/virtualserversvps.com\/blog\/fail2ban-jail-regex-tarpit-single-vcpu-cost\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/fail2ban-jail-regex-tarpit-single-vcpu-cost\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/virtualserversvps.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Fail2Ban on a Busy VPS: Jail Regex Cost, Tarpit Behaviour and CPU You Didn&#8217;t Budget For\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#website\",\"url\":\"https:\/\/virtualserversvps.com\/blog\/\",\"name\":\"Virtual Servers VPS Blog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/virtualserversvps.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0\",\"name\":\"Virtual-Servers-Vps-Editor\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g\",\"caption\":\"Virtual-Servers-Vps-Editor\"},\"sameAs\":[\"https:\/\/virtualserversvps.com\/blog\"],\"url\":\"https:\/\/virtualserversvps.com\/blog\/author\/virtualserversvps\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Fail2Ban on a Busy VPS: Jail Regex Cost, Tarpit Behaviour and CPU You Didn't Budget For - Virtual Servers VPS Blog","description":"Fail2Ban installations are cheap to add and expensive to keep cheap. The package itself idles at a few megabytes, but each jail you enable spawns a log","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/virtualserversvps.com\/blog\/fail2ban-jail-regex-tarpit-single-vcpu-cost\/","og_locale":"en_US","og_type":"article","og_title":"Fail2Ban on a Busy VPS: Jail Regex Cost, Tarpit Behaviour and CPU You Didn't Budget For","og_description":"Fail2Ban on a Busy VPS: Jail Regex Cost, Tarpit Behaviour and CPU You Didn't Budget For","og_url":"https:\/\/virtualserversvps.com\/blog\/fail2ban-jail-regex-tarpit-single-vcpu-cost\/","og_site_name":"Virtual Servers VPS Blog","article_published_time":"2026-10-26T22:12:09+00:00","author":"Virtual-Servers-Vps-Editor","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Virtual-Servers-Vps-Editor","Est. reading time":"7 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/virtualserversvps.com\/blog\/fail2ban-jail-regex-tarpit-single-vcpu-cost\/","url":"https:\/\/virtualserversvps.com\/blog\/fail2ban-jail-regex-tarpit-single-vcpu-cost\/","name":"Fail2Ban on a Busy VPS: Jail Regex Cost, Tarpit Behaviour and CPU You Didn't Budget For - Virtual Servers VPS Blog","isPartOf":{"@id":"https:\/\/virtualserversvps.com\/blog\/#website"},"datePublished":"2026-10-26T22:12:09+00:00","author":{"@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0"},"description":"Fail2Ban installations are cheap to add and expensive to keep cheap. The package itself idles at a few megabytes, but each jail you enable spawns a log","breadcrumb":{"@id":"https:\/\/virtualserversvps.com\/blog\/fail2ban-jail-regex-tarpit-single-vcpu-cost\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/virtualserversvps.com\/blog\/fail2ban-jail-regex-tarpit-single-vcpu-cost\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/virtualserversvps.com\/blog\/fail2ban-jail-regex-tarpit-single-vcpu-cost\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/virtualserversvps.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Fail2Ban on a Busy VPS: Jail Regex Cost, Tarpit Behaviour and CPU You Didn&#8217;t Budget For"}]},{"@type":"WebSite","@id":"https:\/\/virtualserversvps.com\/blog\/#website","url":"https:\/\/virtualserversvps.com\/blog\/","name":"Virtual Servers VPS Blog","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/virtualserversvps.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0","name":"Virtual-Servers-Vps-Editor","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g","caption":"Virtual-Servers-Vps-Editor"},"sameAs":["https:\/\/virtualserversvps.com\/blog"],"url":"https:\/\/virtualserversvps.com\/blog\/author\/virtualserversvps\/"}]}},"_links":{"self":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/1517","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/comments?post=1517"}],"version-history":[{"count":1,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/1517\/revisions"}],"predecessor-version":[{"id":1518,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/1517\/revisions\/1518"}],"wp:attachment":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/media?parent=1517"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/categories?post=1517"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/tags?post=1517"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}