Modern web APIs must handle unpredictable traffic patterns. Whether caused by legitimate traffic spikes, aggressive clients, or malicious attacks, excessive requests can overwhelm an application, degrade performance, and impact other users.
Rate limiting is a critical technique for protecting APIs by controlling how many requests a client can make within a given period. ASP.NET Core includes built-in rate limiting middleware that provides flexible and high-performance request throttling without requiring third-party libraries.
In this article, you'll learn how to implement production-ready rate limiting in ASP.NET Core 11, explore different rate limiting algorithms, and understand how to evaluate their effectiveness using a structured testing methodology.
Note: This article focuses on implementation and testing methodology. It does not include fabricated benchmark results.
Why Rate Limiting Matters
Without rate limiting, applications may experience:
API abuse
Denial-of-service attempts
Excessive resource consumption
Database overload
Unfair resource usage
Increased infrastructure costs
Rate limiting helps ensure fair access while protecting backend services.
Common Rate Limiting Algorithms
ASP.NET Core supports multiple algorithms.
| Algorithm | Best For | Characteristics |
|---|---|---|
| Fixed Window | Simple APIs | Easy to configure |
| Sliding Window | General APIs | Smoother traffic distribution |
| Token Bucket | Burst traffic | Allows temporary bursts |
| Concurrency Limiter | Long-running requests | Limits simultaneous requests |
Choosing the appropriate algorithm depends on your workload.
Create a Web API
dotnet new webapi -n RateLimitingDemo
The built-in rate limiting middleware is available through ASP.NET Core.
Configure Rate Limiting
Register the service.
using System.Threading.RateLimiting;
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter("api", config =>
{
config.PermitLimit = 100;
config.Window = TimeSpan.FromMinutes(1);
config.QueueLimit = 20;
config.QueueProcessingOrder =
QueueProcessingOrder.OldestFirst;
});
});
This configuration allows 100 requests per minute while queuing a limited number of additional requests.
Enable the Middleware
var app = builder.Build();
app.UseRateLimiter();
app.MapControllers();
app.Run();
The middleware should be added before endpoints that require rate limiting.
Apply a Policy
Apply the configured policy to an endpoint.
app.MapGet("/products", () =>
{
return Results.Ok("Success");
})
.RequireRateLimiting("api");
Only requests matching the configured limits are processed.
Fixed Window Limiter
A fixed window counts requests during a defined time interval.
Example:
Minute 1
██████████ 100 Requests
Minute 2
Counter Reset
It is simple and efficient but may allow bursts near window boundaries.
Sliding Window Limiter
Configure a sliding window.
builder.Services.AddRateLimiter(options =>
{
options.AddSlidingWindowLimiter("sliding", config =>
{
config.PermitLimit = 100;
config.Window = TimeSpan.FromMinutes(1);
config.SegmentsPerWindow = 6;
});
});
Instead of resetting all counters at once, the sliding window gradually expires older requests.
This produces smoother traffic handling.
Token Bucket Limiter
Configure a token bucket.
builder.Services.AddRateLimiter(options =>
{
options.AddTokenBucketLimiter("token", config =>
{
config.TokenLimit = 100;
config.TokensPerPeriod = 20;
config.ReplenishmentPeriod =
TimeSpan.FromSeconds(10);
});
});
The token bucket algorithm allows short bursts while maintaining a controlled average request rate.
Concurrency Limiter
Limit simultaneous request execution.
builder.Services.AddRateLimiter(options =>
{
options.AddConcurrencyLimiter("concurrent", config =>
{
config.PermitLimit = 25;
config.QueueLimit = 50;
});
});
This strategy is particularly useful for expensive operations such as file uploads or report generation.
Rate Limiting by Client
Policies can be partitioned using a client identifier.
builder.Services.AddRateLimiter(options =>
{
options.AddPolicy("clients", context =>
RateLimitPartition.GetFixedWindowLimiter(
context.Connection.RemoteIpAddress?.ToString()
?? "unknown",
_ => new FixedWindowRateLimiterOptions
{
PermitLimit = 50,
Window = TimeSpan.FromMinutes(1)
}));
});
Other partition keys may include:
API key
User ID
Tenant ID
JWT claim
Choose a partition strategy that aligns with your authentication model.
Custom Rejection Response
Customize responses for rejected requests.
builder.Services.AddRateLimiter(options =>
{
options.OnRejected = async (context, _) =>
{
context.HttpContext.Response.StatusCode = 429;
await context.HttpContext.Response.WriteAsync(
"Too many requests.");
};
});
Returning HTTP 429 clearly communicates that the client has exceeded the configured limit.
End-to-End Request Flow
A typical request follows these steps:

Join the conversation! Your thoughts help the community grow.