{"id":49080,"date":"2025-04-11T13:57:19","date_gmt":"2025-04-11T13:57:19","guid":{"rendered":"https:\/\/www.devopsschool.com\/blog\/?p=49080"},"modified":"2025-04-11T13:57:34","modified_gmt":"2025-04-11T13:57:34","slug":"comprehensive-guide-to-solving-429-too-many-requests-in-laravel-and-microservices","status":"publish","type":"post","link":"https:\/\/www.devopsschool.com\/blog\/comprehensive-guide-to-solving-429-too-many-requests-in-laravel-and-microservices\/","title":{"rendered":"Comprehensive Guide to Solving 429 Too Many Requests in Laravel and Microservices"},"content":{"rendered":"\n<h1 class=\"wp-block-heading\">\ud83d\udeab What is HTTP 429 &#8211; Too Many Requests?<\/h1>\n\n\n\n<p>HTTP 429 means the client has sent too many requests in a given amount of time (rate limiting). Laravel triggers this when requests exceed configured limits \u2014 typically to protect your app from abuse or overloading.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udd27 Common Scenarios Causing 429 in Laravel<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Multiple users behind the same IP (e.g., via NAT, load balancer, or Docker)<\/li>\n\n\n\n<li>API calls from microservices hitting Laravel endpoints<\/li>\n\n\n\n<li>Performance testing or bots<\/li>\n\n\n\n<li>Poorly configured <code>throttle<\/code> middleware<\/li>\n\n\n\n<li>Mobile or IoT apps making rapid requests<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udcc4 Laravel Rate Limiting Basics<\/h2>\n\n\n\n<p>Laravel uses the <code>ThrottleRequests<\/code> middleware:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">Route::middleware(<span class=\"hljs-string\">'throttle:60,1'<\/span>)-&gt;group(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">()<\/span> <\/span>{\n    Route::get(<span class=\"hljs-string\">'\/api\/data'<\/span>, <span class=\"hljs-string\">'DataController@index'<\/span>);\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This allows <strong>60 requests per minute per user\/IP<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u2705 Solutions to Fix or Improve Rate Limiting<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\u2705 1. Increase Rate Limit Per Route<\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">Route::middleware(<span class=\"hljs-string\">'throttle:300,1'<\/span>)-&gt;get(<span class=\"hljs-string\">'\/api\/resource'<\/span>, <span class=\"hljs-string\">'ResourceController@index'<\/span>);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Allows 300 requests per minute<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\u2705 2. Define Custom Rate Limiters (Laravel 8+)<\/h3>\n\n\n\n<p>In <code>RouteServiceProvider.php<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Support<\/span>\\<span class=\"hljs-title\">Facades<\/span>\\<span class=\"hljs-title\">RateLimiter<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Cache<\/span>\\<span class=\"hljs-title\">RateLimiting<\/span>\\<span class=\"hljs-title\">Limit<\/span>;\n\nRateLimiter::for(<span class=\"hljs-string\">'custom-api'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">($request)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> Limit::perMinute(<span class=\"hljs-number\">200<\/span>)-&gt;by(optional($request-&gt;user())-&gt;id ?: $request-&gt;ip());\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Then use:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">Route::middleware(<span class=\"hljs-string\">'throttle:custom-api'<\/span>)-&gt;group(...);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\u2705 3. Throttle Based on User ID (Not IP)<\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">Limit::perMinute(<span class=\"hljs-number\">300<\/span>)-&gt;by($request-&gt;user()?-&gt;id ?: $request-&gt;ip());\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>This prevents shared IPs (like containers or offices) from hitting the global rate limit.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\u2705 4. Disable Rate Limiting for Internal IPs<\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">RateLimiter::for(<span class=\"hljs-string\">'api'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">($request)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">if<\/span> (in_array($request-&gt;ip(), &#91;<span class=\"hljs-string\">'127.0.0.1'<\/span>, <span class=\"hljs-string\">'172.20.0.2'<\/span>])) {\n        <span class=\"hljs-keyword\">return<\/span> Limit::none();\n    }\n    <span class=\"hljs-keyword\">return<\/span> Limit::perMinute(<span class=\"hljs-number\">60<\/span>);\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\u2705 5. Handle 429 Gracefully in Frontend\/Microservices<\/h3>\n\n\n\n<p>Add retry logic:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">if<\/span> (response.status === <span class=\"hljs-number\">429<\/span>) {\n    <span class=\"hljs-keyword\">const<\/span> retryAfter = response.headers&#91;<span class=\"hljs-string\">'retry-after'<\/span>] || <span class=\"hljs-number\">1<\/span>;\n    <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Promise<\/span>(<span class=\"hljs-function\"><span class=\"hljs-params\">r<\/span> =&gt;<\/span> setTimeout(r, retryAfter * <span class=\"hljs-number\">1000<\/span>));\n    retryRequest();\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\u2705 6. Use Laravel Job Queues for Heavy APIs<\/h3>\n\n\n\n<p>Offload rate-heavy tasks to queues:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">dispatch(<span class=\"hljs-keyword\">new<\/span> ProcessWebhook($data));\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Avoid synchronous spikes by spreading processing.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\u2705 7. Track and Log 429 Errors<\/h3>\n\n\n\n<p>Use global exception handler:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">render<\/span><span class=\"hljs-params\">($request, Throwable $exception)<\/span>\n<\/span>{\n    <span class=\"hljs-keyword\">if<\/span> ($exception <span class=\"hljs-keyword\">instanceof<\/span> ThrottleRequestsException) {\n        Log::warning(<span class=\"hljs-string\">'Rate limit exceeded'<\/span>, &#91;\n            <span class=\"hljs-string\">'ip'<\/span> =&gt; $request-&gt;ip(),\n            <span class=\"hljs-string\">'url'<\/span> =&gt; $request-&gt;url(),\n        ]);\n    }\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">parent<\/span>::render($request, $exception);\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\u2705 8. Use Redis for Smarter Limiting<\/h3>\n\n\n\n<p>Laravel supports Redis-backed rate limits:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-string\">'limiter'<\/span> =&gt; env(<span class=\"hljs-string\">'CACHE_DRIVER'<\/span>, <span class=\"hljs-string\">'redis'<\/span>),\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Redis allows burst handling and high-speed checks.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\u2705 9. Use API Gateway or Reverse Proxy Rate Limiting<\/h3>\n\n\n\n<p>If using services like:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>NGINX: <code>limit_req_zone<\/code>, <code>limit_req<\/code><\/li>\n\n\n\n<li>AWS API Gateway: rate\/usage plans<\/li>\n\n\n\n<li>Cloudflare: Rate Limiting Rules<\/li>\n<\/ul>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Apply rate limits before Laravel even sees the request.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\u2705 10. Implement Client Token Quotas<\/h3>\n\n\n\n<p>Track usage per API token\/user key:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Use Laravel Passport or Sanctum<\/li>\n\n\n\n<li>Store request counts in Redis\/DB<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\ude80 Microservices Specific Tips<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Problem<\/th><th>Solution<\/th><\/tr><\/thead><tbody><tr><td>Burst API requests<\/td><td>Queue or cache throttle<\/td><\/tr><tr><td>Services on same IP<\/td><td>Use unique user tokens per service<\/td><\/tr><tr><td>Stateless retry loops<\/td><td>Add jitter or exponential backoff<\/td><\/tr><tr><td>API aggregator overload<\/td><td>Add inter-service caching<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udd0e Inspect Rate Limit Headers<\/h2>\n\n\n\n<p>Laravel adds these headers:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>X-RateLimit-Limit<\/code><\/li>\n\n\n\n<li><code>X-RateLimit-Remaining<\/code><\/li>\n\n\n\n<li><code>Retry-After<\/code><\/li>\n<\/ul>\n\n\n\n<p>Log or inspect them to tune performance or debug issues.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udd04 Summary<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Solution<\/th><th>Use Case<\/th><\/tr><\/thead><tbody><tr><td><code>throttle:200,1<\/code><\/td><td>General increase<\/td><\/tr><tr><td>Custom limiter<\/td><td>User-based, IP-based<\/td><\/tr><tr><td>Disable for internal IPs<\/td><td>Microservices, local traffic<\/td><\/tr><tr><td>Queuing heavy jobs<\/td><td>Async deferral<\/td><\/tr><tr><td>Redis backend<\/td><td>Fast &amp; scalable<\/td><\/tr><tr><td>Gateway limits<\/td><td>Layer 7 protection<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83c\udf1f Final Thoughts<\/h2>\n\n\n\n<p>429 errors protect your app \u2014 but when wrongly configured, they block real users and services. Laravel gives you the tools to fine-tune rate limits by IP, user, or context. For microservices, coordination is key: throttle at the API gateway and queue jobs internally.<\/p>\n\n\n\n<p>Let me know if you want examples with Laravel Sanctum, Redis-backed rate limits, or job queue implementations!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\ud83d\udeab What is HTTP 429 &#8211; Too Many Requests? HTTP 429 means the client has sent too many requests in a given amount of time (rate limiting). Laravel triggers this&#8230; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"_joinchat":[],"footnotes":""},"categories":[2],"tags":[],"class_list":["post-49080","post","type-post","status-publish","format-standard","hentry","category-uncategorised"],"_links":{"self":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/49080","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/comments?post=49080"}],"version-history":[{"count":2,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/49080\/revisions"}],"predecessor-version":[{"id":49082,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/49080\/revisions\/49082"}],"wp:attachment":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/media?parent=49080"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/categories?post=49080"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/tags?post=49080"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}