Performance & maintenance
Laravel Performance Optimization: How to Handle More Concurrent Users
Learn Laravel performance optimization in 7 steps: find bottlenecks, fix slow queries, cache, queues and Octane to handle more concurrent users.

Why Laravel performance optimization matters for your business
A Laravel site that feels fast with a few visitors can slow down when many people use it at once. Those visitors are your concurrent users, and a slow site loses them before they read a single word.
Most Laravel sites are not slow because the code is bad. Usually one bottleneck holds everything back, such as slow database queries, repeated work on every request, or heavy pages.
This guide to Laravel performance optimization shows how to find that bottleneck, apply the quick wins, use Laravel Octane to serve more requests from one server, and plan for growth beyond it.
Quick answer: To optimize Laravel performance, measure first to find the bottleneck, then fix slow database queries, cache expensive results, run php artisan optimize on every deploy and move slow work to queues. To handle more concurrent users from the same server, add Laravel Octane.
Find what is slowing your app down
Fix slow database queries
Cache expensive results
Run
php artisan optimizeon every deployMove slow work to queues and trim your front end
Use Laravel Octane to handle more concurrent requests
Plan for growth beyond one server
Step 1: Find what is slowing your Laravel app down
Measure first, so you fix the real problem and not a guess.
Page speed: Run your key pages through PageSpeed Insights to see how they perform for real visitors.
Slow queries: Use
DB::listento log each query with its time, andDB::whenQueryingForLongerThanto get a callback when one request spends too long on the database.Slow requests: Laravel Pulse can help you spot slow requests and queries on the server.
Database load: The
db:monitorcommand can raise an event when your database has too many open connections.
Write down the numbers before you change anything, so you can tell what actually helped.
Step 2: Fix slow database queries
The database is the most common cause of a slow Laravel page, so check it before anything else.
N+1 queries. This happens when your code loads a list of records and then runs one extra query for each record to get something related. Eager loading with with() fixes it:
// Slow: one query for the books, then one more query per book
$books = Book::all();
foreach ($books as $book) {
echo $book->author->name;
}
// Fast: two queries in total
$books = Book::with('author')->get();To catch these early, call Model::preventLazyLoading(! $this->app->isProduction()) in your AppServiceProvider. It throws an error in development whenever code lazy loads a relationship, while production keeps working normally.
Loading more than you need. Use withCount('comments') when you only need a number, and load specific columns with with('author:id,name') when you don't need the whole record. Paginate long lists with paginate() instead of loading every row.
Missing indexes. Add a database index to columns you filter, sort or join on. In a migration, $table->index('status'); does it.
Step 3: Cache expensive results
If every visitor triggers the same slow query or calculation, store the result once and reuse it. Cache::remember runs your code only when the cached value is missing or has expired:
$paidOrders = Cache::remember('paid-orders-count', 600, function () {
return Order::where('status', 'paid')->count();
});The 600 is the lifetime in seconds. Use a fast cache driver such as Redis in production, and remove the cached value with Cache::forget('paid-orders-count') when the underlying data changes.
Step 4: Run php artisan optimize on every deploy
Laravel can cache your configuration, events, routes and views, so it doesn't rebuild them on every request. One command does all four, and it belongs in your deployment process:
php artisan optimizeThe php artisan optimize:clear command removes those caches again.
One warning: once the configuration is cached, the .env file is no longer loaded. Calls to env() outside your config files then return null, so read environment values in config files and use config() everywhere else.
Step 5: Move slow work to queues and trim your front end
Visitors shouldn't wait for work they don't need to see. Sending emails, generating PDFs, resizing images and calling slow external services can all run in the background. Dispatch a job, return the response, and let a queue worker finish the task:
SendInvoiceEmail::dispatch($order);Queue workers are long-running processes, so keep them alive with Supervisor and restart them after each deploy so they load your new code.
On the front end, compress and resize images, serve them through a CDN, and build your CSS and JavaScript for production with your build tool, such as Vite. Large images are one of the most common causes of slow pages, and no server setting fixes them.
Step 6: Use Laravel Octane to handle more concurrent requests
Once your queries, caching and deployment are in order, Octane lets the same server serve more requests, because it starts your application once instead of on every request. The sections below cover what it does, which server to choose, how to set it up and what to watch out for. A dedicated, in-depth Octane guide is coming soon.
What is Laravel Octane and how does it work?
Laravel Octane is an official Laravel package that boots your application once, keeps it in memory, and then passes it each incoming request.
With the traditional PHP setup, the framework starts up, loads its service providers and shuts down again for every visitor. Think of a kitchen that prepares all its ingredients from scratch for every single order. Octane prepares them once at the start of the shift, so each order only needs cooking.
Octane runs this through a fast application server instead of the usual PHP process model. It supports three servers: Swoole (Open Swoole is supported too), RoadRunner and FrankenPHP. Each server runs several workers, and each worker handles many requests before it restarts.
How much faster will Laravel Octane make your site?
Octane shortens the time your server spends getting ready to answer. Pages start loading sooner, and the same server can usually handle more visitors before it struggles.
It is not a cure for everything. Octane only speeds up the server side of a page load. Slow database queries, oversized images, heavy JavaScript and slow third-party services stay slow.
How much faster your site gets depends on your application, so measure before and after on your own project. I deliberately don't quote a speed-up figure here, because a number from someone else's app says little about yours.
Swoole vs RoadRunner vs FrankenPHP: which Octane server should you choose?
All three servers keep your app in memory, so the choice comes down to setup and extra features, not the basic speed-up idea.
Swoole | RoadRunner | FrankenPHP | |
|---|---|---|---|
What it is | A PHP extension (Open Swoole is also supported, with the same Octane features) | A server binary built with Go | A PHP application server written in Go |
How you get it | Install the extension with PECL | Octane offers to download the binary on first start | Octane downloads the binary when you choose it, or use the official Docker images |
Extras | Concurrent tasks, ticks and intervals, the Octane cache, Swoole tables | Standard Octane features | Early hints, Brotli and Zstandard compression, custom Caddyfile, HTTPS with HTTP/2 and HTTP/3 |
Good fit when | You want Swoole-only features | You want a binary instead of a PHP extension | You want modern web features or a Docker-based deployment |
If you don't need the Swoole-only features, RoadRunner and FrankenPHP are the easier starts, because Octane fetches their binaries for you. Swoole is the one to pick when you want to run tasks in parallel or use the very fast Octane cache.
How to install and set up Laravel Octane
Install the package, then run the installer and pick your server:
composer require laravel/octane
php artisan octane:installStart the server. It listens on port 8000 by default:
php artisan octane:startBecause the app stays in memory, code changes are not picked up until the workers restart. In development, --watch restarts the server on file changes. It needs Node and the Chokidar library:
npm install --save-dev chokidar
php artisan octane:start --watchTwo options are worth knowing about. By default Octane starts one worker per CPU core, and --workers changes that. Octane also restarts each worker after 500 requests as a safety net against memory leaks, and --max-requests changes that number.
php artisan octane:start --server=frankenphp --workers=4 --max-requests=250Running Laravel Octane in production
Keep the process alive with a process monitor such as Supervisor, so Octane restarts if it stops.
Put Nginx or Apache in front. It serves static files and handles SSL, and passes everything else to Octane on port 8000.
If you serve the site over HTTPS, set
OCTANE_HTTPS=trueso generated links start withhttps://.After every deployment, run
php artisan octane:reloadso the workers load your new code.
[program:octane]
command=php /path/to/your-app/artisan octane:start --server=frankenphp --host=127.0.0.1 --port=8000
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/path/to/your-app/storage/logs/octane.logCommon Laravel Octane mistakes and how to avoid them
Octane resets Laravel's own state between requests, but it can't know about state your own code keeps. These are the problems I'd check for first:
Stale objects in singletons. A singleton built during boot lives across requests. If it holds the container, the request or the config repository, it can end up with outdated data, such as another visitor's request.
Growing static data. Adding to a static array on every request leaks memory, because the array is never cleared. Watch memory use while developing.
Old code after a deploy. Workers keep the previous code in memory until they are reloaded.
The fix for the first problem is usually to pass the data in when you call a method, instead of storing it in the object:
// Risky: the request is captured once at boot and reused
$this->app->singleton(Service::class, fn ($app) => new Service($app['request']));
// Safer: pass what you need at call time
$service->method($request->input('name'));The global request(), config() and app() helpers always return the current instance, so they are safe to use. The 500-request worker restart limits the damage from a leak, but it does not remove the cause.
When Laravel Octane is not the right fit
Octane is not always the best next step. Hold off if one of these applies:
The slowness is somewhere else. If pages wait on slow queries, missing indexes or an external API, Octane won't fix that. Find the real bottleneck first.
Your hosting can't run a long-lived process. Octane needs a server process that stays running, usually under Supervisor. Many shared hosting plans don't allow that.
Your code leans on global state. Heavy use of static properties or packages that store state between calls may need rework before they are safe.
The site is small and quiet. For a low-traffic site, the extra server work may not be worth a gain nobody will notice.
Step 7: Plan for growth beyond one server
A well-tuned server handles a lot of traffic, but it has a limit. When you reach it, these are the usual next moves:
Give the database its own server. Once the database competes with your application for CPU and memory, a separate or managed database lets each one scale on its own.
Share state between servers. Sessions, cache, queues and uploaded files need a home that every application server can reach, such as Redis or shared file storage, instead of one server's disk.
Add a load balancer. It spreads traffic across several application servers. Laravel's built-in health route,
/upby default, lets a load balancer check whether each server is working.Consider a managed platform. Laravel Cloud and Laravel Forge take over much of the server work if you would rather not run it all yourself.
Laravel performance optimization checklist
Use this list to work through your own project in order:
Measure first with PageSpeed Insights, query logging and Laravel Pulse.
Fix N+1 queries with eager loading and add indexes to columns you filter or sort on.
Cache slow queries and calculations, using Redis in production.
Run
php artisan optimizeon every deploy, and keepenv()calls inside config files.Move emails, PDFs and external API calls to queues.
Compress images, use a CDN and build your assets for production.
Keep PHP and Laravel up to date. Laravel 13 requires PHP 8.3 or newer.
Add Laravel Octane when you need more concurrent requests and your hosting can run a persistent process.
Measure again after each change, so you know which one actually helped.
Need help speeding up your Laravel site?
Most slow Laravel sites have one or two clear bottlenecks, and measuring first finds them quickly. Octane is the right next step once your queries, caching and deployment are in order and your hosting can run a persistent process.
If you aren't sure where your site's bottleneck is, I can look at your project and tell you honestly. I work directly on every Laravel and Next.js build, with no agency in between. Book a free call or send me a message.
To see performance work on real Laravel projects, read my case studies on OSHE London Academy and Treat Expert. If you want ongoing help, see my web development services and maintenance plans, or learn more about me.
Laravel Octane FAQ
What is Laravel Octane?
Laravel Octane is an official Laravel package that boots your application once and keeps it in memory. It serves requests through Swoole, Open Swoole, RoadRunner or FrankenPHP.
Does Laravel Octane make my site faster?
It removes the repeated startup work from every request, so server responses are usually quicker and the same server can handle more visitors. It does not fix slow database queries, large images or heavy JavaScript, and the gain depends on your application.
Which Octane server should I choose?
Choose Swoole if you want concurrent tasks, ticks and the Octane cache. RoadRunner and FrankenPHP are easier to start with, because Octane downloads their binaries for you.
Do I need to change my code to use Octane?
Often very little, but check any singleton that stores the container, the request or the config, and any static property that grows over time. Those are the usual sources of stale data and memory leaks.
Can I run Laravel Octane on shared hosting?
Usually not, because Octane needs a server process that stays running. The typical production setup is a server where you can run Supervisor and put Nginx or Apache in front.
How do I deploy new code while Octane is running?
Run php artisan octane:reload after each deployment. This restarts the workers gracefully so they load your new code.

