Rate Limiting por IP en ASP.NET Core (.NET 8): 429 Too Many Requests
Limita solicitudes por IP con el rate limiting integrado de .NET 8 y devuelve 429 con Retry-After para proteger tu API del abuso.
En APIs públicas, uno de los problemas más comunes son los picos de solicitudes (request spikes). Un bot, un cliente con errores o una lógica de reintentos demasiado agresiva puede generar muchísimas peticiones en poco tiempo. Una de las primeras capas de defensa es aplicar rate limiting: permitir solo cierto número de solicitudes en una ventana de tiempo.
Con .NET 8, esto se puede implementar de forma moderna, sin escribir un middleware propio, usando el
middleware de rate limiting integrado.
En este ejemplo aplicamos un límite por IP y, cuando se excede, devolvemos 429 Too Many Requests.
Además añadimos el header Retry-After para indicar al cliente cuándo debería reintentar.
Objetivo
- Limitar solicitudes por dirección IP.
- Devolver
429 Too Many Requestscuando se exceda el límite. - Añadir
Retry-Afterpara que los clientes apliquen un backoff de forma correcta. - Mantener la configuración simple y reutilizable en endpoints.
Program.cs (Fixed Window Rate Limiting por IP)
Esta configuración permite 60 solicitudes por minuto por IP.
Cuando se supera el límite, el middleware devuelve 429 y nosotros añadimos Retry-After.
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddRateLimiter(options =>
{
// Código de estado por defecto para solicitudes rechazadas
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
// Nombre de la policy: "ip"
options.AddPolicy("ip", httpContext =>
{
var ip = GetClientIp(httpContext);
// Partición por IP: cada IP obtiene su propio limiter
return RateLimitPartition.GetFixedWindowLimiter(
partitionKey: ip,
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 60, // 60 solicitudes
Window = TimeSpan.FromMinutes(1), // por 1 minuto
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 0 // sin cola: rechazar inmediatamente
});
});
// Añadir Retry-After cuando se rechaza
options.OnRejected = async (context, ct) =>
{
context.HttpContext.Response.Headers["Retry-After"] = "60";
await context.HttpContext.Response.WriteAsync("Demasiadas solicitudes.", ct);
};
});
var app = builder.Build();
app.UseRateLimiter();
app.MapControllers()
.RequireRateLimiting("ip");
app.Run();
static string GetClientIp(HttpContext httpContext)
{
// Si estás detrás de un reverse proxy, revisa la nota más abajo.
return httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
}
¿Dónde aplicar la policy?
En el ejemplo anterior aplicamos rate limiting a todos los controllers con
MapControllers().RequireRateLimiting("ip").
Si quieres aplicarlo solo en endpoints específicos, puedes configurarlo a nivel de endpoint.
Prueba rápida
Puedes simular un pico de solicitudes (Linux/macOS) así:
for i in {1..80}; do
curl -s -o /dev/null -w "%{http_code}\n" https://localhost:5001/api/customers
done
Después de la solicitud 60 (dentro del mismo minuto), deberías ver respuestas 429.
Nota importante: reverse proxy y la IP real del cliente
Si tu API está detrás de un reverse proxy (NGINX, Cloudflare, Azure App Gateway, etc.),
RemoteIpAddress puede contener la IP del proxy en lugar de la IP real del cliente.
En ese caso, el rate limiting por IP no funcionará correctamente a menos que configures
los forwarded headers.
En ASP.NET Core puedes habilitar forwarded headers así (en producción, confía solo en proxies conocidos):
using Microsoft.AspNetCore.HttpOverrides;
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
// IMPORTANTE: en producción, añade IPs / redes de proxies conocidos
// options.KnownProxies.Add(IPAddress.Parse("10.0.0.10"));
});
var app = builder.Build();
app.UseForwardedHeaders();
Cuando los forwarded headers están correctamente configurados, RemoteIpAddress reflejará la IP real del cliente.
Mejoras comunes
- Usar límites diferentes por endpoint (por ejemplo, un límite más estricto para login).
- Hacer whitelist de IPs internas o excluir health checks.
- Si ejecutas varias instancias, usar un store distribuido (los límites en memoria son por instancia).
- Devolver un error estructurado (por ejemplo, ProblemDetails) en lugar de texto plano.
TL;DR
- El rate limiting por IP es una forma simple de proteger APIs contra picos y abuso.
- .NET 8 ofrece rate limiting integrado con
AddRateLimiter()+UseRateLimiter(). - Cuando se excede el límite, devuelve
429y añadeRetry-After. - Detrás de un proxy, configura forwarded headers para obtener la IP real del cliente.