A Laravel application can run well during development and become slow after it reaches production. The cause is often not the PHP code itself, but the way the runtime is configured, the number of database queries generated by each request, and work that is performed during the request even though it could run in the background. These are the first areas worth checking before changing the application code.
Where PHP and Laravel Performance Problems Usually Come From
Modern PHP is fast enough for many business applications, so the interpreter is often not the main bottleneck. Request time is more commonly spent waiting for database queries, external API calls, file operations, or network I/O. Measuring these parts first is usually more useful than immediately refactoring PHP code.
Start with measurement rather than refactoring. Laravel Telescope can help during staging, Debugbar is useful during development, and database slow-query logs can reveal expensive queries. It is also worth keeping PHP on a supported version, both for security updates and for the performance improvements available in newer releases.
Configure PHP Runtime and PHP-FPM Before Optimising Code
Two runtime settings deserve attention early. OPcache keeps compiled bytecode in memory so PHP does not need to compile scripts on every request. In production, allocate enough OPcache memory for the application and make sure the deployment process refreshes the cache when new code is released. Timestamp validation can also be disabled when application files do not change between deployments.
PHP-FPM worker sizing should be based on actual memory usage rather than a fixed number copied from another server. Estimate how much memory a worker uses under load, compare it with the memory available on the machine, and leave enough room for the database and cache when they share the host. Too many workers can cause swapping during traffic spikes, while too few workers can leave available CPU and memory unused.
PHP JIT is more relevant to computation-heavy workloads than to typical web applications. Laravel applications often spend more time waiting for databases, APIs, and other I/O than performing CPU-intensive calculations. Test JIT against the actual workload before enabling it, and do not treat it as a replacement for database or request optimisation.
Check Database Query Count Before Optimising PHP Code
One of the most common Laravel performance problems is the N+1 query pattern. A list of records can trigger an additional query for each related record, which may be difficult to notice with a small development dataset but becomes expensive as the database grows. Eloquent makes this pattern easy to introduce, and eager loading is usually the first fix to consider.
| Symptom | Usual cause | Practical fix |
|---|---|---|
| Response time grows with the number of rows displayed | Relations loaded lazily inside a loop | Load relations in advance with eager loading, and enable strict mode in development so lazy loading raises an exception |
| A single endpoint uses a large amount of memory | All records retrieved into memory at once | Paginate the result, or process the data in chunks when it is a background task |
| Queries are fast individually but the page is slow | Hundreds of small queries per request | Reduce the number of queries rather than optimising each one, and cache results that rarely change |
| One query is slow and the rest are fine | Missing index on the filtered or sorted column | Read the execution plan and add an index that matches the actual conditions, not every column |
| The application slows down only at certain times | Reports or exports running against the production database during business hours | Move heavy reads to a queue, a schedule outside peak hours, or a read replica |
Selecting only the columns that are used is a smaller but worthwhile habit, particularly for tables that contain large text fields. It reduces both database work and the memory each request consumes, which in turn affects how many workers the server can support.
Move Slow Work to Laravel Queues
Work that does not need to finish before the HTTP response should normally run outside the request. Email delivery, document generation, third-party API calls, file processing, and webhook delivery are common queue jobs. Laravel supports queues with Redis or a database backend, while Horizon provides visibility for Redis-based queues. Failed jobs should also be reviewed regularly.
This also affects application availability. If an external API becomes slow and the call runs inside the request, PHP-FPM workers remain occupied until the call finishes. Enough slow requests can exhaust the available workers even when the dependency has not completely failed. Set timeouts on outbound HTTP calls, and move calls that do not need an immediate result into a queue job.
An application can become unavailable when it waits too long for a dependency outside its control.
Scheduled work follows the same principle. Laravel expects a single cron entry that runs the scheduler every minute, with the individual tasks defined in the application. Tasks that can overlap should be prevented from doing so explicitly, and any job that writes to external systems should be written to tolerate being retried, since queue workers retry by design.
Use Laravel Cache With a Clear Invalidation Strategy
Configuration, route, and view caching are normally part of the deployment process. After configuration is cached, application code should not read environment variables directly outside configuration files. Keep environment-specific values in configuration and access them through the application's configuration layer.
Application-level caching needs an explicit expiration and invalidation strategy. Without one, users can receive stale data and the resulting behaviour can be difficult to reproduce. Reference data, navigation structures, permission lists, and expensive aggregate queries are common candidates. Data that users expect to change immediately after an action usually needs a different approach or an explicit cache invalidation step.
Deploy Laravel Changes Safely in Production
- 1Install dependencies for production with development packages excluded and the autoloader optimised, ideally during a build step rather than on the production server.
- 2Run configuration, route, view, and event caching after the new code is in place, so the cached files match the release being activated.
- 3Treat database migrations as a separate consideration from code. Additive changes such as new nullable columns are safe to apply before the release, while destructive changes should follow once no running code depends on the old structure.
- 4Activate the new release atomically, using a symlink switch or an equivalent mechanism, so no request is ever served from a partially updated directory.
- 5Restart queue workers after every deployment. Workers are long-running processes and continue executing the previous version of the code until they are restarted, which produces confusing behaviour that looks like a caching problem.
- 6Reload PHP-FPM so that OPcache picks up the new code, and confirm that storage and cache directories remain writable by the web server user after the release.
Laravel Production Security Settings
- Debug mode must be disabled in production. The Laravel error page is a valuable development tool and it exposes configuration values and environment details to anyone who triggers an exception.
- The application key should be generated once per environment and kept out of version control, because it protects encrypted values and signed data.
- Mass assignment protection exists for a reason. Model attributes that come from user input should be listed explicitly rather than opened up when a form grows.
- Validation belongs in form request classes rather than scattered through controllers, which keeps rules reviewable and consistent between endpoints that accept the same data.
- Uploaded files should be validated by type and size and stored outside the public directory, with access served through the application when the content is not meant to be public.
- Rate limiting should be applied to authentication and to any endpoint that triggers work or sends messages, since these are the endpoints that get abused first.
- Dependencies should be audited regularly, and the PHP version itself should remain within the window that still receives security fixes.
When Laravel Is a Good Fit
Laravel is well suited to many business applications, internal systems, administrative interfaces, and APIs, especially when requirements evolve during development. Its ecosystem includes authentication, queues, scheduling, notifications, and testing, which reduces the amount of infrastructure that needs to be assembled separately.
Laravel may require a different approach for computation-heavy workloads, very low-latency systems, or architectures dominated by large numbers of long-lived connections. Laravel Octane can improve some workloads by keeping the application in memory between requests, but it also requires careful handling of application state. In practice, runtime configuration, database access, and background processing often have a larger effect on a Laravel application's performance than changing frameworks.
What a Well-Configured Laravel Production Deployment Looks Like
In a well-configured deployment, response times remain predictable as data grows because query counts are controlled. Slow third-party services are isolated so they do not block the entire application. Deployments are repeatable, queue workers are restarted when new code is released, and failed jobs are reviewed. Logs and error tracking also contain enough context to investigate production issues.
These practices do not require unusual architecture. They require deliberate runtime configuration, careful database access, and moving slow work out of the request path. Together, these areas cover many of the practical issues that affect Laravel applications as production traffic and data volume increase.
Key takeaways
- Measure before optimising. Most Laravel performance problems are query count and blocking calls rather than PHP execution speed.
- Configure OPcache and size PHP-FPM workers against available memory before considering code-level optimisation.
- Move email, exports, third-party calls, and any slow work into queues, and always set timeouts on outbound HTTP requests.
- Make deployment repeatable, restart queue workers on every release, and keep debug mode disabled in production.