If your Laravel app is slow, the reason is almost always the database, not the framework. Specifically: N+1 queries, missing indexes, missing eager-loading, missing caching, and Eloquent patterns that look clean in the controller but generate forty queries under the hood. Fix those, and the same endpoint moves from a 2-second p95 to under 200 milliseconds. We have done this dozens of times.

The honest summary: Laravel is not slow. Slow Laravel apps are slow because of the same seven decisions on every project. Below we walk through them in order of impact — with concrete code patterns and the order to apply them.

How to measure first

Before you fix anything, install Laravel Debugbar (development) or Laravel Telescope (any environment) and look at where time actually goes per request. Record the query count, the slowest queries, the total query time, and the p50 / p95 / p99 response times on your top three endpoints. These are the numbers you will compare against after each fix.

The number that matters most for backend APIs is p95 latency. Median (p50) hides the slow tail; p99 is too noisy on small traffic. p95 is the right balance. A healthy Laravel API endpoint hits p95 under 200 ms; a slow one hits p95 over 1 second.

Fix 1 — Eliminate N+1 queries

The single biggest source of slowness in Laravel apps is the N+1 query problem. The pattern: you fetch a collection of models, then loop over them and access a relationship, which fires a new query per model. Fetching 50 users and accessing $user->posts triggers 1 query for users + 50 queries for posts = 51 queries.

The fix is eager loading via the with() method: User::with('posts')->get() fires 2 queries regardless of how many users. For nested relationships: User::with('posts.comments')->get() fires 3 queries. Laravel's relationship loading is genuinely the easiest part of the framework once you know to look for N+1.

Enable the strict mode in development to catch this automatically: Model::preventLazyLoading() in your AppServiceProvider. Any lazy-loaded relationship now throws an exception, forcing you to eager-load it explicitly.

Fix 2 — Add database indexes

Every query that filters or orders by a column should have an index on that column. Every foreign key should be indexed. Every column used in a WHERE clause on a table with more than a few thousand rows should be indexed.

The fix is a database audit. EXPLAIN every slow query (Telescope shows these). Look for “Using filesort” or “Using temporary” or sequential scans on tables larger than 10k rows. Add migrations that create indexes on the slow columns. Test query time before and after; the difference is usually 10x to 1000x.

Pay special attention to compound indexes for queries that filter on multiple columns. A query like where('tenant_id', X)->where('status', 'active') wants a compound index on (tenant_id, status), not separate indexes on each column.

Fix 3 — Cache aggressively

The fastest query is the one you do not run. Laravel's cache facade (Redis-backed) makes caching genuinely cheap to add: Cache::remember('dashboard.stats.'.$user->id, 600, fn () => expensiveQuery()) caches the result for 10 minutes.

What to cache: dashboard aggregations that recompute the same numbers every page load, expensive resource transformations, anything that hits external APIs, complex collection mappings that don't change per-user. What NOT to cache: user-specific data that changes frequently, anything that needs to be real-time, anything that shows up in a list with pagination.

Tag your caches so you can invalidate them precisely: Cache::tags(['user.'.$user->id])->remember(...). When the user changes, flush Cache::tags(['user.'.$user->id])->flush().

The fastest query is the one you do not run. The cheapest fix to a slow Laravel app is to cache the expensive parts and move on.

Fix 4 — Move slow work to queues

Anything that takes more than a few hundred milliseconds and does not need to complete before the response should be queued. Sending emails, generating PDFs, calling external APIs, processing uploads, recalculating analytics — all of these belong in jobs, not in the request lifecycle.

Laravel's queue system (with Redis or database driver, monitored via Horizon) makes this trivial: SendNotification::dispatch($user) returns immediately, the worker processes the job in the background, the user sees a fast response. We routinely cut p95 latency 40 to 70% by moving synchronous work to queues.

Set queue priorities and concurrency carefully. High-priority queues for user-facing notifications (10 workers). Low-priority queues for batch reports (2 workers). Horizon makes this configurable per environment.

Fix 5 — Optimise Eloquent

Eloquent is convenient but sometimes too convenient. Common patterns that quietly cost performance:

Loading all columns when you need three. User::all() hydrates every column on every model. Use User::select('id', 'name', 'email')->get() when you only need three fields. The memory difference at scale is meaningful.

Loading models when you need raw data. For reports and aggregations, use the query builder directly (DB::table('users')) rather than Eloquent. You skip model hydration, observer firing, and accessor evaluation.

Counting with collections. $users->count() on a collection is fine; User::get()->count() loads every user into memory first. Use User::count() which runs SELECT COUNT(*) directly.

Pluck for single columns. User::pluck('email') returns a collection of strings without hydrating models. Dramatically faster for “give me all the X” queries.

Fix 6 — Use Laravel Octane (where appropriate)

Laravel Octane runs your application as a long-lived process (via Swoole, Open Swoole, or FrankenPHP) rather than booting fresh on every request. The bootstrap cost (50 to 150 ms in traditional PHP-FPM) drops to 0. Total p95 latency typically improves 5 to 10x on simple endpoints.

The catch: Octane keeps your application in memory, which means state persists between requests. Singleton services, static properties, and any in-memory caches need to be either properly scoped per-request or explicitly cleared. We run a “boot health check” on every Octane deploy to catch state-leak bugs.

For APIs with high traffic, Octane is worth the migration. For low-traffic admin tools, the traditional PHP-FPM model is fine and simpler to operate.

Fix 7 — Profile the production database

The last fix is about your infrastructure, not your code. Run pg_stat_statements (PostgreSQL) or the slow query log (MySQL) for a week. Look at the queries that consume the most total time across all calls. Often the answer is one or two specific queries running thousands of times that each take 50 ms — collectively eating most of your database's capacity.

The fix varies: add indexes, rewrite the query, denormalise a column, add a materialised view, cache the result. The diagnostic is the same: find the top 5 queries by total time, fix them one by one.

What good looks like after the seven fixes

A well-tuned Laravel application in 2026 hits these numbers:

p95 API latency under 200 ms on real production traffic.

Query count under 10 per request on read endpoints; under 20 on write endpoints.

Cache hit ratio above 80% on cacheable endpoints.

Queue backlog under 100 jobs at any given moment; processing time under 5 minutes for the slowest job class.

Memory per request under 32 MB on traditional PHP-FPM; significantly higher on Octane but consistent across requests.

These are the targets we hold every Laravel build to. Performance is not optional; it is part of the build.

Common questions

How much will fixing all seven things improve my Laravel app?

On a typical slow Laravel app (p95 of 1 to 2 seconds, frequent timeouts under load), all seven fixes together usually move p95 to under 200 ms and unlock 3 to 10x the throughput before the database becomes a bottleneck again. Specific gains depend on starting state: apps with N+1 problems see the largest gains from eager-loading and indexes; apps with synchronous external calls see the largest gains from queuing; apps with hot dashboards see the largest gains from caching.

Is Laravel Octane safe to run in production?

Yes, when applied carefully. Octane has been production-stable since Laravel 8 and is now widely used by serious Laravel applications. The risks are around state leaks (singletons, static properties, in-memory caches) that persist between requests because the process stays in memory. We follow a checklist before any Octane deploy: audit every singleton service, clear request-scoped state in middleware, run integration tests under Octane mode, and gate the deploy behind a health check. Done properly, Octane is reliable and significantly faster.

How long does a Laravel performance engagement take?

Audit alone is typically 3 to 5 working days. Implementation depends on the seven fixes the audit prioritises and the starting state. A typical engagement is 2 to 4 weeks of part-time work, often delivered while the existing app stays live and changes are tested on staging first. Octane migrations add 1 to 2 weeks for the testing and state-audit work. Most clients see p95 latency halve in the first two weeks of focused work.

Should I use Redis or Memcached for Laravel caching?

Redis. Laravel works well with both but Redis has become the default for good reasons: data structures beyond simple key-value (lists, sorted sets, hashes), persistence options, pub/sub, and it doubles as your queue backend, session store, and broadcast channel. Memcached is faster on pure key-value reads but does not give you the broader infrastructure benefits. We use Redis on every Laravel build for cache, queue, session, and broadcast. One service does four jobs.

Does Laravel performance affect SEO?

Indirectly. Laravel renders APIs and back-end logic; the frontend is what Google sees. But slow API endpoints make the frontend slow because the page waits for data. We have measured 15 to 25% organic traffic lift in the first 90 days after Laravel API optimisation work on content-heavy applications, because Core Web Vitals on the frontend improved when API calls got faster.

Want a free Laravel performance audit?

Send us a repository URL or staging access. We will send back a 2-page audit showing your current p95 latency, the top query offenders, and the seven highest-impact fixes for your app specifically.


Request a performance audit