35+ Years Experience Netherlands Based ⚡ Fast Response Times Ruby on Rails Experts AI-Powered Development Fixed Pricing Available Senior Architects Dutch & English 35+ Years Experience Netherlands Based ⚡ Fast Response Times Ruby on Rails Experts AI-Powered Development Fixed Pricing Available Senior Architects Dutch & English
Rate Limiting voor je Rails API met Rack::Attack (Stap voor Stap)

Rate Limiting voor je Rails API met Rack::Attack (Stap voor Stap)

roger
Zo voeg je rate limiting toe aan een Rails 8 API met Rack::Attack. Throttling, blocklists, custom responses en productieconfiguratie met Redis en Solid Cache.

Een onbeschermde Rails API is een open uitnodiging. Scrapers, credential stuffers en die ene client die elke 200 milliseconden pollt vinden je vanzelf. Rate limiting stopt ze voordat ze je database opvreten.

Deze guide laat zien hoe je rate limiting toevoegt aan een Rails 8 API met Rack::Attack, de meest beproefde throttling-middleware voor Rack-apps. Binnen een kwartier heb je werkende rate limits.

Rack::Attack Installeren

Voeg de gem toe:

# Gemfile
gem "rack-attack", "~> 6.7"
bundle install

Configureer Rails om de middleware te gebruiken. In Rails 8 voeg je dit toe aan je application config:

# config/application.rb
config.middleware.use Rack::Attack

Cache Store Configureren

Rack::Attack heeft een cache-backend nodig om requests te tellen. In productie wil je iets dat gedeeld wordt tussen processen. Redis is de standaardkeuze, maar Rails 8’s Solid Cache werkt ook.

# config/initializers/rack_attack.rb
Rack::Attack.cache.store = ActiveSupport::Cache::RedisCacheStore.new(
  url: ENV.fetch("REDIS_URL", "redis://localhost:6379/1"),
  expires_in: 10.minutes
)

Als je Solid Cache al gebruikt en geen Redis-dependency wilt:

Rack::Attack.cache.store = Rails.cache

Zorg dat je config.cache_store op :solid_cache_store of :redis_cache_store staat — niet op :null_store, anders wordt er niets bijgehouden.

Basis Throttling Instellen

Een throttle-regel definieert een rate limit: hoeveel requests van dezelfde bron binnen een tijdsvenster.

# config/initializers/rack_attack.rb

# Beperk alle requests per IP tot 300 per 5 minuten
Rack::Attack.throttle("req/ip", limit: 300, period: 5.minutes) do |req|
  req.ip
end

Wanneer een client meer dan 300 requests doet in 5 minuten, krijgen volgende requests een 429 Too Many Requests response tot het venster reset.

Aparte Limieten voor API Endpoints

Je API-endpoints hebben waarschijnlijk andere limieten nodig dan je webpagina’s. Een login-endpoint moet veel strenger zijn dan een productlijst:

# Strikte limiet op authenticatie-endpoints
Rack::Attack.throttle("logins/ip", limit: 5, period: 20.seconds) do |req|
  req.ip if req.path == "/api/v1/sessions" && req.post?
end

# Standaard API-limiet
Rack::Attack.throttle("api/ip", limit: 100, period: 1.minute) do |req|
  req.ip if req.path.start_with?("/api/")
end

Het blok retourneert een discriminator — de waarde die identificeert wie er gethrottled wordt. Retourneer nil om de regel over te slaan voor dat request.

Throttlen op API Key in Plaats van IP

IP-gebaseerde throttling werkt niet goed achter load balancers of wanneer meerdere clients een IP delen. Als je API token-authenticatie gebruikt, throttle dan op token:

Rack::Attack.throttle("api/token", limit: 1000, period: 1.hour) do |req|
  if req.path.start_with?("/api/")
    req.env["HTTP_AUTHORIZATION"]&.remove("Bearer ")
  end
end

Zo krijgt elke API-client een eigen rate limit bucket.

Blocklists en Safelists

Soms wil je bekende kwaadwillenden volledig blokkeren, of monitoring-services uitzonderen:

# Blokkeer requests van specifieke IP's
Rack::Attack.blocklist("block bad actors") do |req|
  bad_ips = Rails.cache.fetch("rack_attack:bad_ips", expires_in: 1.hour) do
    BlockedIp.pluck(:address)
  end
  bad_ips.include?(req.ip)
end

# Throttle nooit requests van je monitoring
Rack::Attack.safelist("allow monitoring") do |req|
  req.ip == ENV["MONITORING_IP"]
end

Safelists worden eerst gecontroleerd. Als een request matcht met een safelist, worden alle throttles en blocklists overgeslagen.

De 429 Response Aanpassen

De standaard 429 response is kaal. Voor een API wil je JSON teruggeven met rate limit details:

Rack::Attack.throttled_responder = lambda do |request|
  match_data = request.env["rack.attack.match_data"]
  now = match_data[:epoch_time]
  retry_after = match_data[:period] - (now % match_data[:period])

  headers = {
    "Content-Type" => "application/json",
    "Retry-After" => retry_after.to_s,
    "X-RateLimit-Limit" => match_data[:limit].to_s,
    "X-RateLimit-Remaining" => "0",
    "X-RateLimit-Reset" => (now + retry_after).to_s
  }

  body = {
    error: "Rate limit exceeded",
    retry_after: retry_after
  }.to_json

  [429, headers, [body]]
end

De Retry-After header vertelt goed gedragende clients precies wanneer ze het opnieuw kunnen proberen.

Rate Limit Events Bijhouden

Je wilt weten wanneer throttling actief wordt. Rack::Attack stuurt ActiveSupport notifications:

# config/initializers/rack_attack.rb
ActiveSupport::Notifications.subscribe("throttle.rack_attack") do |name, start, finish, request_id, payload|
  req = payload[:request]
  Rails.logger.warn(
    "[Rack::Attack] Throttled #{req.ip} on #{req.path} " \
    "(matched: #{req.env['rack.attack.matched']})"
  )
end

Stuur deze naar je logging setup zodat ze in je monitoring dashboard verschijnen.

Rate Limits Testen

Rack::Attack biedt een test helper die de cache leegt tussen tests:

# spec/requests/rate_limiting_spec.rb
require "rails_helper"

RSpec.describe "Rate limiting", type: :request do
  before { Rack::Attack.cache.store.clear }

  it "throttles excessive API requests" do
    101.times { get "/api/v1/products", headers: { "REMOTE_ADDR" => "1.2.3.4" } }

    expect(response.status).to eq(429)
    expect(response.parsed_body["error"]).to eq("Rate limit exceeded")
  end
end

Productie-overwegingen

Reverse proxy IP forwarding. Als Rails achter Nginx of een load balancer zit, kan request.ip altijd 127.0.0.1 zijn. Zorg dat je proxy X-Forwarded-For instelt en configureer Rails:

# config/environments/production.rb
config.action_dispatch.trusted_proxies = ActionDispatch::RemoteIp::TRUSTED_PROXIES + [
  IPAddr.new("10.0.0.0/8")
]

Fail open, niet closed. Als je Redis uitvalt, kan Rack::Attack geen requests tellen. Standaard faalt het open (laat al het verkeer door) — de juiste keuze. Een kapotte rate limiter mag je API niet platleggen.

Cache expiry is belangrijk. Stel de expires_in van je cache store in op minstens zo lang als je langste throttle-periode.

Deploy geleidelijk. Begin met ruime limieten en monitor het 429-percentage in je logs. Verscherp als je je verkeerspatronen begrijpt. Ik heb teams gezien die agressieve rate limits op dag één deployen en hun eigen mobiele app buitensloten omdat die gefaalde requests in een strakke loop herhaalde.

Als Rack::Attack Niet Genoeg Is

Rack::Attack regelt rate limiting op applicatieniveau. Maar bij DDoS-achtig verkeer raken de requests nog steeds je Ruby-proces voordat ze worden geweigerd. Voor volumetrische aanvallen, rate limit op infrastructuurniveau:

  • Nginx limit_req_zone — blokkeert requests voordat ze Puma bereiken
  • Cloudflare / AWS WAF — blokkeert aan de edge
  • Kamal 2 met Thruster — de standaard proxy regelt basis rate limiting in de Go-laag

Combineer je verdedigingslagen. Rack::Attack vangt misbruik dat door je edge-bescherming heen komt.

FAQ

Hoe rate limit ik een Rails API zonder Redis?

Gebruik Rails 8’s Solid Cache als Rack::Attack cache store door Rack::Attack.cache.store = Rails.cache in te stellen met config.cache_store = :solid_cache_store. Solid Cache slaat rate limit counters op in je database. De performance is iets lager dan Redis bij veel verkeer, maar voldoende voor de meeste applicaties.

Welke rate limit moet ik instellen voor een Rails API?

Er is geen universeel antwoord. Begin met 100 requests per minuut per IP voor algemene API-endpoints en 5 requests per 20 seconden voor authenticatie-endpoints. Monitor je 429-percentage een week en pas dan aan.

Werkt Rack::Attack met Rails 8?

Ja. Rack::Attack 6.7+ werkt met Rails 8 en Ruby 3.3+. Voeg config.middleware.use Rack::Attack toe aan config/application.rb en configureer het in een initializer. Het integreert met alle Rails 8 cache stores inclusief Solid Cache.

Hoe geef ik rate limit headers terug in Rails?

Configureer Rack::Attack.throttled_responder om custom headers terug te geven. Voeg Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining en X-RateLimit-Reset toe. Deze volgen de IETF draft standaard voor HTTP rate limit headers.

Kan ik verschillende rate limits instellen per API-client?

Ja. Throttle op API key of token in plaats van IP-adres. Retourneer het token vanuit het throttle-blok als discriminator. Elke unieke discriminator krijgt een eigen counter, dus elke API-client heeft een onafhankelijk rate limit bucket.

#ruby-on-rails #api #security #performance
r

About the Author

Roger Heykoop is een senior Ruby on Rails ontwikkelaar met 19+ jaar Rails ervaring en 35+ jaar ervaring in softwareontwikkeling. Hij is gespecialiseerd in Rails modernisering, performance optimalisatie, en AI-ondersteunde ontwikkeling.

Get in Touch

Share this article

Need Expert Rails Development?

Let's discuss how we can help you build or modernize your Rails application with 19+ years of expertise

Schedule a Free Consultation