Article guideContents, topics, tags, and RSS
ASP.NET Core includes rate-limiting middleware, so a current application does not need a custom action filter backed by a process-local memory cache. The middleware supports fixed-window, sliding-window, token-bucket, and concurrency limiters, plus named policies that can be applied to selected endpoints.
Rate limiting protects capacity and makes abusive or accidental traffic predictable. It is not a replacement for authentication, authorization, input validation, upstream network protection, or a dedicated DDoS service.
Register a named policy
This fixed-window policy allows five requests every ten seconds and rejects excess requests immediately:
using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
options.AddFixedWindowLimiter("short-window", limiter =>
{
limiter.PermitLimit = 5;
limiter.Window = TimeSpan.FromSeconds(10);
limiter.QueueLimit = 0;
limiter.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
limiter.AutoReplenishment = true;
});
});
var app = builder.Build();
app.UseRouting();
app.UseRateLimiter();
app.MapControllers();
app.Run();
With endpoint-specific policies, call UseRateLimiter after UseRouting. The current ASP.NET Core rate-limiting documentation covers middleware order and the available algorithms in detail.
Apply the policy to an endpoint
Use EnableRateLimitingAttribute on a controller or action:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
[ApiController]
[Route("api/[controller]")]
public class ActionsController : ControllerBase
{
[HttpGet("limited")]
[EnableRateLimiting("short-window")]
public IActionResult GetLimited()
{
return Ok(new { message = "OK" });
}
}
You can also attach a policy while mapping an endpoint:
app.MapGet("/api/limited", () => Results.Ok(new { message = "OK" }))
.RequireRateLimiting("short-window");
When the limit is exceeded, the middleware returns 429 Too Many Requests. A client should treat that response as a signal to slow down, not immediately retry in a tight loop.
Partition limits deliberately
A single global counter is rarely the right production policy. Depending on the endpoint, partition requests by an authenticated account, API key, tenant, client application, or another trustworthy identifier.
An IP address can be useful for coarse anonymous limits, but proxies, carrier-grade NAT, IPv6 privacy addresses, and spoofable forwarding headers make it an imperfect identity. Only use forwarded headers after configuring trusted proxies.
For a multi-instance application, remember that an in-process limiter controls each application instance independently. If the business requirement is a strict shared quota, enforce it at a shared gateway or use infrastructure designed for distributed limits.
Choose the right algorithm
- Fixed window: simple quotas such as 100 requests per minute, with bursts possible at the boundary.
- Sliding window: smoother behavior around window boundaries.
- Token bucket: allows controlled bursts while replenishing capacity over time.
- Concurrency: limits simultaneous work rather than requests over a period.
Start with a measurable capacity or product rule, log rejections, and tune the policy from real traffic. A very low limit added without observing legitimate clients can turn your protection into an outage.
