rack-attack: Rack Middleware for Blocking and Throttling Abusive Requests
Rack middleware for blocking & throttling
At a glance
- What is it?
- rack-attack is a Ruby gem that lets Rails and Rack applications decide when to allow, block or throttle a request. This review covers its rule model, cache-backed counters, install steps and the cases where it is the wrong layer.
- Who is it for?
- Adopt rack-attack when you already run a Rack or Rails app and want request-level allow, block and throttle rules without pushing that logic into a reverse proxy. Skip it if your traffic is terminated before Rack, if you need cross-service rate limits, or if you cannot give it a shared cache store: the README states that Fail2Ban and throttle counters default to Rails.cache and that a memory store will not coordinate across processes.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 21 days ago.
- What is it written in?
- Mainly Ruby, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What rack-attack actually decides, and for whom
rack-attack sits in the Rack middleware stack and inspects each request before your application code runs. The README describes it as middleware for blocking and throttling abusive requests, and the decision surface is narrow on purpose: for a given request it can allow it, block it, or count it against a throttle. It is aimed at teams running Rails or another Rack-based Ruby application who want that logic expressed in Ruby rather than in nginx or a CDN rule set.
The important default is stated bluntly in the README: rack-attack performs no blocking or throttling until you configure rules. Installing the gem changes nothing about how your app behaves. That is a sensible design for a library that can lock users out, but it also means the gem provides no baseline protection on its own. If you add it and walk away, you have added a middleware layer and zero policy.
It is not a WAF. There is no signature database, no request body inspection described in the README, and no managed rule feed. It is a Ruby DSL over request properties, which is why the rules read like application code.
Safelists, blocklists and throttles: the rule model
Every rule is a named block that receives a Rack::Request. A safelist block returns truthy to allow; a blocklist block returns truthy to block; a throttle block returns a discriminator string, and requests sharing that string are counted together. The README also exposes IP shortcuts such as safelist_ip and blocklist_ip, which accept either a single address or a subnet string like "1.2.3.0/24".
Precedence is explicit: safelists win. The README states that any request matching a safelist is allowed even if it also matches blocklists or throttles. That ordering matters in practice, because a broad safelist for an internal range or a trusted API key silently disables every other rule for those requests. A common failure mode is a safelist written for convenience during debugging and never removed.
The blocklist examples in the README are deliberately simple: block everything under /admin, or block POST requests to /login whose user agent equals a specific string. Because the block receives the full Rack::Request, the actual predicate can be as complex as you want, but the gem does not parse or normalize anything for you. Header matching goes through request.env keys, as in the README's HTTP_APIKEY example.
Fail2Ban and Allow2Ban as stateful counters
The Fail2Ban pattern is the most interesting part of the design. Instead of a static blocklist, a blocklist block calls Rack::Attack::Fail2Ban.filter with a discriminator, a maxretry count and a findtime window. The README's example blocks all requests from an IP for a period after three blocked requests within ten minutes, using a discriminator of "pentesters-#{req.ip}".
The README is explicit that Fail2Ban state lives in a configurable cache, and that the cache defaults to Rails.cache when present. That single sentence carries most of the operational weight of this feature. In a multi-process deployment, a per-process memory store means each worker counts independently, so an attacker hitting N workers gets roughly N times the retries. The README does not spell out a recommended shared store, and the cache store configuration section is where you have to look.
The README also warns that for multiple filters you should put each filter in its own blocklist and use a unique discriminator per filter. Ignoring that advice collapses distinct counters into one namespace, which produces blocks that fire earlier than any single rule intended.
Installing rack-attack and writing a first rule
The README gives two install paths. Add the gem to your Gemfile with the version constraint it shows, then run bundle. Alternatively install it directly with gem install.
# In your Gemfile
gem "rack-attack", "~> 6.8"For a Rails application the README says rack-attack is used by default once the gem is present. For a plain Rack application you must require it and mount it in config.ru.
# In config.ru
require "rack/attack"
use Rack::AttackRules go in a file that runs during application initialization. For Rails the README points to config/initializers/rack_attack.rb. A first rule can be a blocklist for a path prefix, which is the smallest thing that visibly changes behaviour.
# config/initializers/rack_attack.rb
Rack::Attack.blocklist("block all access to admin") do |request|
request.path.start_with?("/admin")
endAfter restarting the app, requests whose path begins with /admin should be blocked rather than reaching the router. The README notes that Rack::Attack.enabled = false disables the middleware entirely, which is useful in test cases and worth knowing before you debug a rule that never fires.
Where rack-attack is the wrong tool
The middleware only sees requests that reach Rack. If a CDN, load balancer or nginx already rejects traffic, rack-attack never runs, and no rule you write will change that. The README does not describe any integration with an upstream proxy, so coordination between the two layers is your problem.
Counters are per cache store, not per cluster of services. Two applications sharing an attacker but not sharing a cache will each apply the full retry budget. Nothing in the README suggests a distributed coordination mechanism beyond pointing the cache at a shared backend.
There is also a real risk in the rule model itself. Because every rule is arbitrary Ruby evaluated per request, a slow predicate runs on every request. The README has a Performance section in its table of contents, but the excerpt does not include its contents, so the specific guidance there is something you should read in the repository rather than assume. Finally, rack-attack is request-level policy. It does not authenticate users, and the safelist example based on a header value is only as trustworthy as the header itself.
rack-attack compared with fail2ban and proxy-level limits
The Fail2Ban helper is explicitly inspired by fail2ban, and the difference in approach is worth stating. fail2ban reads log files on the host and manipulates firewall rules, so it operates below the application and can block traffic before Ruby is involved. rack-attack evaluates in-process, so it can reason about request paths, headers, user agents and any Ruby expression, but it pays that cost inside your application's request cycle and depends on your cache for state.
Proxy-level rate limiting, for example nginx limit_req or a CDN rule, has the same lower-layer advantage and the same blindness to application concepts. It cannot easily distinguish an authenticated user from an anonymous one, because that knowledge lives in your app. rack-attack can, which is why the safelist-by-header example exists.
The practical split is that rack-attack handles policy that needs application context, and the layer in front handles volumetric filtering. Using rack-attack as the only defence against a large flood means every blocked request still consumed a Ruby process.
Maintenance, releases and licence
The repository is not archived, and the last push was on 2026-09-08. Release history is uneven rather than steady: v6.6.1 in April 2022, v6.7.0 in August 2023, and v6.8.0 in October 2025. The gap between v6.7.0 and v6.8.0 is over two years, so teams planning upgrades should track the CHANGELOG rather than assume a predictable cadence.
The README itself carries a warning at the top: the default branch README may document unreleased features, and it points to the 6-stable branch README for the version matching the latest release. If you read documentation from GitHub's default view, you may be reading about behaviour that is not in the gem you installed. That is a concrete reason to check the stable branch README against your pinned version.
The gem is MIT licensed. Under MIT terms you can use, modify and redistribute it, including in commercial applications, provided the copyright notice and permission notice are preserved. This is a summary of the licence identifier, not legal advice; read the LICENSE file for the actual terms.
Upgrade cost is mostly configuration drift. Rules live in your initializer, not in the gem, so a version bump does not rewrite them, but the cache store configuration and any Fail2Ban discriminators are yours to re-verify after an upgrade.
Editorial conclusion
Adopt rack-attack when you already run a Rack or Rails app and want request-level allow, block and throttle rules without pushing that logic into a reverse proxy. Skip it if your traffic is terminated before Rack, if you need cross-service rate limits, or if you cannot give it a shared cache store: the README states that Fail2Ban and throttle counters default to Rails.cache and that a memory store will not coordinate across processes. Before writing rules, verify which cache store your app actually resolves and confirm that Rack::Attack.enabled is true in the environment you are testing.
Frequently asked questions
What is rack-attack?
It is a Rack middleware gem for blocking and throttling abusive requests in Rails and Rack applications. It lets you define safelist, blocklist and throttle rules in Ruby, evaluated against each incoming request.
Does rack-attack block anything by default?
No. The README states that by default rack-attack will not perform any blocking or throttling until you configure rules to protect against specific behaviour.
How do I install the rack-attack gem?
Add gem "rack-attack", "~> 6.8" to your Gemfile and run bundle, or install it directly with gem install rack-attack. Rails applications use it by default once the gem is present; plain Rack apps need require "rack/attack" and use Rack::Attack in config.ru.
Where does rack-attack store Fail2Ban state?
The README says Fail2Ban state is stored in a configurable cache, which defaults to Rails.cache if present. That means the store you configure determines whether counters are shared across processes.
Which rules take precedence in rack-attack?
Safelists have the most precedence. The README states that any request matching a safelist is allowed despite matching any number of blocklists or throttles.
Can I turn rack-attack off temporarily?
Yes. The README shows Rack::Attack.enabled = false, which disables it permanently for an environment or temporarily, for example in specific test cases.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/rack-rack-attack)