All articles

PHP and Laravel in Production: Performance, Queues, and Deployment

Practical guidance for running PHP and Laravel in production, covering PHP-FPM and OPcache, database queries, queues, caching, deployment, and production security.

Backend Development|Published |10 min read
PHP application code open in an editor during development

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.

SymptomUsual causePractical fix
Response time grows with the number of rows displayedRelations loaded lazily inside a loopLoad 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 memoryAll records retrieved into memory at oncePaginate the result, or process the data in chunks when it is a background task
Queries are fast individually but the page is slowHundreds of small queries per requestReduce the number of queries rather than optimising each one, and cache results that rarely change
One query is slow and the rest are fineMissing index on the filtered or sorted columnRead the execution plan and add an index that matches the actual conditions, not every column
The application slows down only at certain timesReports or exports running against the production database during business hoursMove 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

  1. 1Install dependencies for production with development packages excluded and the autoloader optimised, ideally during a build step rather than on the production server.
  2. 2Run configuration, route, view, and event caching after the new code is in place, so the cached files match the release being activated.
  3. 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.
  4. 4Activate the new release atomically, using a symlink switch or an equivalent mechanism, so no request is ever served from a partially updated directory.
  5. 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.
  6. 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.

Related articles

More articles on software development, AI, cloud, and infrastructure.

A monitoring dashboard showing service availability status
Infrastructure Monitoring|

Uptime Monitoring with Uptime Kuma: A Practical Guide

Learn how to set up Uptime Kuma for reliable uptime monitoring. This guide covers deployment, monitor types, retries, notifications, status pages, and practical maintenance.

Server racks in a data centre under continuous monitoring
Infrastructure Monitoring|

Server Monitoring with Zabbix: A Practical Implementation Guide

Setting up Zabbix is only the first step. A monitoring system becomes useful when alerts are relevant, infrastructure is properly covered, and the team knows how to respond. This guide covers Zabbix architecture, server monitoring, trigger design, alerting, and an implementation approach that can scale with your infrastructure.

Looking for a software development partner?

Tell us about your project, what you need to build, and the challenges you are facing. We can discuss the technical approach, scope, timeline, and estimated cost.

Start a conversation