The first time your Phoenix API goes viral isn’t when you celebrate.
It’s when you worry.
That’s when you think about:
- Abuse
- Overload
- Fairness
- Survivability
And that’s when rate limiting stops being a nice-to-have.
It becomes critical infrastructure.
Rate Limiting Is the Silent Guardian
No alerts.
No crashes.
Just protection.
From what?
- DoS attacks
- Scraping bots
- Infinite loops
- Misconfigured clients
- Overeager scripts
With no rate limiting, you’re exposed.
With it, you have control.
Phoenix Doesn’t Pick Your Strategy — You Do
Phoenix doesn’t ship with a rate limiter.
And that’s a good thing.
Why?
Because rate limiting is not one-size-fits-all.
Options range from:
- ✅ Simple per-IP counters
- ⚙️ Identity-aware token buckets
- 🔐 Role-specific quotas
- ⚡ Redis-backed shared rate stores
- 🧠 Smart throttlers with burst handling
What Phoenix does give you is a plug pipeline — a perfect place to insert your own limiter.
It All Starts With a Plug
Every request goes through your plug stack before it touches a controller.
So you can:
- Inspect headers
- Extract auth tokens
- Look up API keys
- Apply policies
- Enforce limits
All before anything else happens.
defmodule MyAppWeb.Plugs.RateLimiter do
import Plug.Conn
def call(conn, _opts) do
client_id = extract_client_id(conn)
if over_limit?(client_id) do
conn
|> put_resp_header("retry-after", "60")
|> send_resp(429, "Rate limit exceeded")
|> halt()
else
conn
end
end
end
Strategy 1: Per-IP Rate Limiting
Basic, useful, fast.
Track requests from a single IP over time:
- Store in ETS or Redis
- Reset every minute
- Block if over threshold
Good for:
- Public APIs
- Anonymous endpoints
- Quick-and-dirty protection
Caveat: NAT and proxies can group multiple users under one IP.
Strategy 2: Identity-Based Rate Limiting
Track users, not locations.
Extract a user ID or token from the request:
client_id = conn.assigns[:current_user].id
Or:
client_id = get_req_header(conn, "authorization") |> parse_token()
Now your limit is about who, not where.
Use Redis for:
- Atomic increments
- Expiry
- Cross-node coordination
Strategy 3: Role-Based or Route-Specific Policies
Customize limits:
Group | Limit |
---|---|
Public | 60 requests/min |
Authenticated | 600 requests/min |
Admin | 6,000 requests/min |
Internal | Unlimited |
Control intent, not just speed.
Apply different rules per route or HTTP verb.
Token Buckets & Burst Handling
Some APIs can handle spikes — but not floods.
Token buckets let clients:
- Accumulate tokens at a fixed rate
- Spend them in bursts
- Refill gradually
Useful for:
- Download endpoints
- Search APIs
- Expensive queries
Use libraries like ExRated
or build your own using Redis/ETS.
Observability Matters
You can’t fix what you can’t see.
Log every rejection:
Logger.warn("Rate limit hit", user_id: user.id, ip: conn.remote_ip, path: conn.request_path)
Include headers in responses:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 542
X-RateLimit-Reset: 1718123456
Show users what’s happening.
Make limits transparent.
Make debugging easier.
Soft Throttling vs Hard Limits
Not all rejections need to be blocks.
✅ Hard limit: Return 429 Too Many Requests
.
✅ Soft throttling: Introduce delay but allow the request.
Example:
if over_limit?, do: Process.sleep(200)
It’s not punishment.
It’s system preservation.
Design for Predictability
Bad rate limits surprise people.
Good ones inform them.
That means:
- Clear documentation
- Consistent behavior
- Predictable resets
No one wants a production failure at 3AM because of a quiet 429.
Bonus: Make It a Product Feature
Take it further:
- Build a dashboard showing usage
- Send alerts when nearing limits
- Offer tiered plans with higher caps
This isn’t just backend logic —
It’s a business tool.
Expose rate info through your API:
{
"rate_limit": {
"limit": 1000,
"remaining": 547,
"reset": "2025-06-12T00:00:00Z"
}
}
Rate Limiting Is a Form of Stewardship
You’re not being paranoid.
You’re being a responsible platform owner.
It’s about:
- Keeping things fast
- Protecting your system
- Being fair to all users
- Maintaining uptime under pressure
Phoenix gives you visibility and control.
What you build with that is up to you.
Go Deeper with Phoenix LiveView
If you're serious about building scalable Phoenix apps, get my PDF guide:
📘 Phoenix LiveView: The Pro’s Guide to Scalable Interfaces and UI Patterns
- 20 pages
- Advanced architecture tips
- Reusable LiveView patterns
- Performance strategies
- Real-world examples
A must-read if you care about UI that scales like your backend.
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.