Octane Version
v2.3.10
Laravel Version
v11.6.0
PHP Version
v8.3.6
What server type are you using?
FrankenPHP
Server Version
v1.1.4 PHP 8.3.6 Caddy v2.7.6
Database Driver & Version
Postgress
Description
Last week we updated our app previously using php-fpm
running on Forge to use Laravel Octane with FrankenPHP. Our site is mostly an API that handles analytics events (Like google analytics). It uses the default Laravel api
throttling.
In staging our app worked fine (30 req/sec same IP), but when deploying to production (1400 req/sec, different IPs) it started to fail, giving a lot of 429 Too Many Requests.
I quickly rolled back to php-fpm
and after a few hours tried again with the same problem. Rolled back and the next day I switched to Swoole and it worked perfectly without changing a single line of code nor having to redeploy anything. So I can confidently say that is NOT a bug in my code, but rather a bug with FrankenPHP or the Octane integration with FrankenPHP.
My theory is that the RateLimiter is not reseting between requests so it's shared between different users. So multiple different users trigger the rate limiter:
This is my Rate limiter configuration:
// AppServiceProvider
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
our production CACHE_STORE
is redis
. Throttling worked perfectly fine without octane and with octane but using Swoole. It failed with hundred of 429 Too Many Requests after installing FrankenPHP.
This is our bootstrap/app.php
:
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Support\Facades\App;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
commands: __DIR__.'/../routes/console.php',
health: '/up',
then: function () {
Route::middleware('api')
->prefix('api')
->as('api.')
->domain(config('app.domain'))
->group(base_path('routes/api.php'));
Route::middleware('web')
->domain(config('app.domain'))
->group(base_path('routes/web.php'));
Route::middleware('web')
->domain(config('playsaurus.ads.domain'))
->group(base_path('routes/ads.php'));
}
)
->withMiddleware(function (Middleware $middleware) {
$middleware->throttleApi();
$middleware->redirectTo(
guests: '/login',
users: '/',
);
$middleware->web(append: [
\App\Http\Middleware\HandleInertiaRequests::class,
\Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class,
]);
$middleware->api(append: [
\App\Http\Middleware\ConfigureLocale::class,
]);
$middleware->alias([
'localize' => \App\Http\Middleware\ConfigureLocale::class,
'embed' => \App\Http\Middleware\AllowsEmbeding::class,
]);
})
->withExceptions(function (Exceptions $exceptions) {
$exceptions->dontReport([
\App\Services\Announcements\InvalidVariantKey::class,
\App\Exceptions\CouponRedeemException::class,
]);
})->create();
Steps To Reproduce
It's difficult to reproduce. Because I can't test it in production because that would mean a lot of downtime for our users.
My theory is that it would be possible to reproduce from multiple different IPs. But since I don't have the means to test it, I don't know.