Rate limiting with Apache and mod-security

Rate limiting by request in Apache isn’t easy, but I finally figured out a satisfactory way of doing it using the mod-security Apache module. We’re using it at Brightbox to prevent buggy scripts rinsing our metadata service. In particular, we needed th e ability to allow a high burst of initial requests, as that’s our normal usage pattern. So here’s how to do it.

Install mod-security (on Debian/Ubuntu, just install the libapache2-modsecurity package) and configure it in your virtual host definition like this:

SecRuleEngine On

<LocationMatch "^/somepath">
  SecAction initcol:ip=%{REMOTE_ADDR},pass,nolog
  SecAction "phase:5,deprecatevar:ip.somepathcounter=1/1,pass,nolog"
  SecRule IP:SOMEPATHCOUNTER "@gt 60" "phase:2,pause:300,deny,status:509,setenv:RATELIMITED,skip:1,nolog"
  SecAction "phase:2,pass,setvar:ip.somepathcounter=+1,nolog"
  Header always set Retry-After "10" env=RATELIMITED

ErrorDocument 509 "Rate Limit Exceeded"

This does a few things and has a couple of knobs for you to tweak depending on your requirements. The first SecAction initializes the state, in this case by IP address. You can do this by a cookie if you like, but I needed it done by IP address (if you’re using a reverse proxy of some kind then get the IP from the X-Forwarded-For header here instead).

The second SecAction deprecates the counter by 1 every 1 second. This is setting the base rate of our rate limit, one per second. I’ve named the counter somepathcounter here, feel free to call it what you want and use different names for different rate limiting different parts of your site.

The SecRule checks to see if the counter is greather than 60 and if so it sleeps 300ms, sets the RATELIMITED environment variable and returns a 509 code response. This is setting the burst rate of our rate limit, to 60. Any IP can do a burst of 60 requests as fast as it likes and it then limited to 1 per second. If it makes no further requests for 60 seconds then the counter is decremented back to 0, which means their burst has been fully recharged.

The last SecAction increments the counter for every successful request (the previous SecRule skips this line if the request was rate limited).

Then the Header definition ensures a header is set whenever a request is rate limited, giving a hint to the client that they shouldn’t try again for 10 seconds. This is obviously just a guide and a lot of clients don’t implement it (and it’s really only valid on a 503 status anyway) so it’s a little bit of wishful thinking really.

Then we define a neat ErrorDocument for the 509 response to give a better clue to the client about what is happening.

509 HTTP Response

I chose to use the 509 HTTP code which isn’t a standard (it’s Apache’s own “Bandwidth Limit Exceeded” code). Technically a 503 (with the Retry-After header) is perhaps a better choice but I already use 503 for maintenance pages and I wanted to differentiate between the two responses in the logs. Twitter invented their own status 420 “Enhance Your Calm” code, but that appears to mostly be an elaborate marijuana reference. RFC 6586 defines 429 “Too Many Requests” for this purpose but it’s very new and Apache won’t let you set an ErrorDocument for it yet. And it’s debatable whether this is a 4xx error mode or a 5xx error mode.

mod-security state data

mod-security needs to store the rate limit state between restarts so you need to tell it where to write that data. I create a directory in /var/lib/mod-security but you can just stick it in /tmp if you like:

SecDataDir /tmp

If you don’t define this, mod-security just seems to silently not apply the rate limiting, so you need it even if you don’t care about state between restarts.

It doesn’t appear possible to store the mod-security state in a shared database so you can’t rate limit accurately when you have multiple load balanced web servers.

Sleeping considered bad

This implementation sleeps 300ms per request when the rate has been exceeded but sleeping here is generally not a great idea. It ties up the Apache worker for the duration of the sleep, so whilst it will relieve the load on your backend app (which would otherwise likely use lots of CPU) it arguably makes it easier to tie up all of Apache’s workers and take your site offline.

There are easier ways to tie up a web server though, and without a sleep many clients will just immediately retry the request, spamming the logs with useless messages and using HTTP parsing cpu etc. And without the rate limiting at all, your app might be even slower and could tie things up just as easily. So feel free to tune this as you like.

Basically, remember that this is not a malicious denial of service protection system – we’re just using it enforce a basic policy, most usually breached due to a mistake rather than an attack.

Other options

mod_cband sounds pretty good but I can’t find documentation on how to rate limit requests, just bandwidth, and it’s not packaged for Debian/Ubuntu.

mod_evasive sounds like something you might want to use for DoS protection.

If you’re using NGINX then the HTTP limit req module sounds nice and simple.

Netfilter‘s hashlimit matcher is very powerful and has the benefit of being in a layer before Apache entirely, so you can save even more CPU cycles. Netfilter also has the ability to rate limit logging too, so you won’t fill your disk with useless logs. If you want to return a 509 response, instead of just dropping or rejecting the SYN packet, then you can redirect rate limited connections to an Apache virtual host that just returns 509s (put it on a different port and use the DNAT Netfilter target).

46 thoughts on “Rate limiting with Apache and mod-security”

  1. Hello John,
    Nice info (even it is from 2 years back). I tried your rule with modsec 2.7 and i get the error that there is no id on the rules. Could you update your post to match the current version of modsec?
    Thanks in advance.

    1. I just had the same problem with modsec 2.7. They even have a FAQ entry that explains that the error means you’re using an outdated syntax – but the idiots fail to explain in what way the syntax has changed or at least provide a link to some sort of documentation for the new syntax. That’s probably because the documentation is generally nonexistant, which doesn’t exactly fille me with confidence.
      After some googling: You essentially need to add “id=random-but-unique-numberhere” to each line, e.g.:
      SecAction initcol:ip=%{REMOTE_ADDR},pass,nolog,id:11
      SecAction “phase:5,deprecatevar:ip.somepathcounter=1/1,pass,nolog,id:12″

  2. Tried this but failed :(
    SecRuleEngine On

    SecAction initcol:ip=%{REMOTE_ADDR},pass,nolog,id:887
    SecAction “phase:5,deprecatevar:ip.somepathcounter=1/1,pass,nolog,id:888″
    SecRule IP:SOMEPATHCOUNTER “@gt 60″ “phase:2,pause:300,deny,status:509,setenv:RATELIMITED,skip:1,nolog,889″
    SecAction “phase:2,pass,setvar:ip.somepathcounter=+1,nolog”
    Header always set Retry-After “10” env=RATELIMITED

    ErrorDocument 509 “Rate Limit Exceeded”

    ErrorLog /var/log/httpd/integrator_error_log

  3. Problem: Mod_security is writing the counter to a file with a lock or something. If you get your rate high enough, mod security cannot increase the counter fast enough, so you can break your rate limit easily. So this solution do not protect against DDos. Here you need something like fail2ban, cband or a firewall

  4. Hi,

    Thanks lots for this code.

    I dropped the 300 m/s down to 40 m/s so I could see the rule being hit in modsec_audit.log and saw this error. IP address obfuscated.

    Message: collections_remove_stale: Failed deleting collection (name “ip”, key “″): Internal error
    Apache-Handler: proxy-server
    Stopwatch: 1437655358027830 198518 (- – -)
    Stopwatch2: 1437655358027830 198518; combined=337801, p1=134, p2=329, p3=20, p4=30, p5=168653, sr=47, sw=37673, l=0, gc=130962
    Response-Body-Transformed: Dechunked
    Producer: ModSecurity for Apache/2.7.3 (http://www.modsecurity.org/); OWASP_CRS/2.2.6.
    Server: Apache/2.2.15 (CentOS)
    Engine-Mode: “DETECTION_ONLY”

    Unsure if this is good.

    I amended your rule because it had syntactical errors on my modsec.

    SecRuleEngine On

    SecAction initcol:ip=%{REMOTE_ADDR},pass,nolog,id:11
    SecAction “phase:5,deprecatevar:ip.somepathcounter=1/1,pass,nolog,id:12″
    SecRule IP:SOMEPATHCOUNTER “@gt 60″ “phase:2,pause:300,deny,status:509,setenv:RATELIMITED,skip:1,nolog,id:13″
    SecAction “phase:2,pass,setvar:ip.somepathcounter=+1,nolog,id:14″
    Header always set Retry-After “10” env=RATELIMITED

    ErrorDocument 509 “Rate Limit Exceeded”

    Any advice on the errors I saw?

    Kind regards,

  5. ソースは、私にとって非常に有用であることができます!病気は私のブログにこのWebページへのハイパーリンクを設置しました。イム正の私の訪問者は、非常に便利になります。私はとにかくドメインニュースに役に立ったと評価情報のための大規模なおかげで、私の言語で、このようにたくさんの良い供給ができなくなります。

  6. ポイント あなたはよだけの努力のためにAを受け取ります。 ここであなた真だった私たちを混乱事実あなたの内。 はるかに多くを正確ここであなたが..詳細は作るか、または引数を破る、彼らが言うには、知っていて、それがすることができませんでした。 成果結果私がお届けしましたまさにまさにあなたに明らかにしてみましょう、と述べました。書き込みはかなり説得力のあることができ、それはおそらく、私はの理由でコ​​メントする努力を取ります。それを| 実行やっ私はそれを定期的に習慣にしていません。 ではない、あなたを接続するように見えるどれだけの確信確かに | 私は私が第二に、私は簡単に、あなたが作るロジックでジャンプを気づくことができるが、それは、実際の結論を生成します。今私は自分の位置をサブスクライブしますそれにもかかわらず願い正しいの将来の近くあなた本当にあなたのドットを接続かなり 大きいです。

  7. 。夫と私は、全体のあなたの現在のサイトと一緒にこの投稿を愛して!コンテンツの本物の作品は、一般的に、特に間違いなく楽に許容されるとともに公開されています。あなたの現在のWPのレイアウトは、その上に著しい間違いなくあります!私のパートナーと私は事実を取得することができるだけの場所を知って素晴らしいことになるだろう。さらに、サポートしてください、企業は細かい作業を絶対誰もがネットとはるかに少ないスパマーに自分が含まれ、これらのウェブマスターの多くより多くを必要とします。優れたパートナー!

  8. は、追加の素晴らしいライトアップしていただきありがとうございます。場所を正​​確に誰が執筆の理想的な方法この種の詳細のようなものを得ることができますか?情報と事実、この種の場合に表示させるように、プレゼンテーション、その後の週IVEと、イム。

  9. は、私が実際の点であなたのライトアップを楽しむことができます。私は継続的に市場の専門家が一緒に来て、このビジネスですべてのものを議論するために私は、引数ボードを開発しました。この生産と同じように実際には内のすべての私の人生でした。あなたは私に私自身のウェブサイトのためのいくつかの素晴らしい考えを与えました。

  10. ストックポート。私がお聞きしたかった 1 WPに| 以上にわたってシフトする| 準備計画は | ウェブサイト我々がそうであるようなものが…これはワードプレスのインターネットウェブです。さらに、あなたは、このテンプレートを自分で作るのですか?ありがとうございます。最新筋肉たい方法 |高度な人あなたはなりますあなたがよを

  11. 有料版があるととして効果的に。 Twitterのトレーニング – 入手はツイッターツイッターで開始わずか追加で|通信が前方に突入しているの光の速度、およびアメリカ企業との例(AS よくホームビジネスオンラインのマーケティング担当者など)はアップ| 維持保つに最高の私たちを行う本当にべき。

  12. コメントをここに考えさせられます。ロビンと私は非常に特定のもの以上の記事を通して、あなたのアイデアを気に入っています。私はあなたので、あなたのような個人は、この資料を用いて、実際に私たちのような人々を助けるために行ったように同じくらいの時間がかかったという事実が実際に高く評価され、独自のタイムテーブル上のいくつかの要求を持っていることを実現します。右ここで素晴らしい仕事を

  13. こんにちは、Googleだけを介してあなたのブログにアラートとなり、そのは本当に有益なことがわかりました。私はブリュッセルに気をつけつもりです。あなたは、将来的にこれを続ければ、私は感謝します。多くの人々があなたの文章から利益を得ることになります。乾杯!私はyouveはここで得たものと同じくらい面白いと魅力的ですネット上で何かを発見するために、その稀を

  14. それはだ 純粋クールで便利な価値の一部情報。私はうれしいですあなたその 単に共有この役立つ 詳細私達と。私たちはこのような通知に滞在してください。共有していただきありがとうございます。 対象トピック今までこのことについて出くわす| 私がした私が持っているアドバイス最大最高

  15. リソースは私にとって非常に参考になりますありがとうございました!病気は私のブログに、このページへのハイパーリンクを設置しました。イム正の私の訪問者は、非常に有用な発見します。私はとにかくドメイン情報に役に立ったと評価情報のための巨大なおかげで、私の言語で、このような多くの良い情報源がアレント。このライトアップと上のスポット

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>