Skip to main content

How to Load Test Your PHP Website

How to Load Test Your PHP Website

Key takeaways

  • To load test a PHP website, point a load tool such as Apache JMeter at your real pages with realistic user steps, raise the load in stages, and watch the PHP-FPM pool, the database and response times together.
  • PHP scales by process, so the PHP-FPM worker pool usually caps you before the code does.
  • OPcache, database connections, session locks and slow queries are the other usual ceilings.
  • Test with a warm and a cold cache, because the two numbers differ.

A PHP website serves each request in its own worker process, so the number of workers, and what each one waits for, sets how much traffic you can take. A load test finds that limit before your visitors do. This guide builds the test in Apache JMeter, then shows what to watch on the PHP server while it runs.

What limits a PHP site under load?

Five things cap a typical PHP stack, roughly in the order they bite:

CeilingWhat happensWhere to look
PHP-FPM worker poolOnce every worker is busy, new requests wait in a queue, then time outPHP-FPM status page: listen queue, max children reached
Database connectionsNew requests fail to connect when the server is at its limitMySQL Too many connections errors
Slow queriesWorkers sit waiting on the database, so the pool fills with idle-but-busy workersThe database slow query log
Session locksRequests from one session run one at a timeRequests from the same user queueing
Cold or too-small OPcacheScripts are compiled again on each requestopcache.memory_consumption, opcache.max_accelerated_files

Everything else (framework choice, template engine) usually matters less than these, and you cannot tell which one is yours without a test.

How does PHP-FPM limit concurrency?

PHP-FPM runs a pool of worker processes, and pm.max_children caps the pool. The PHP manual describes it as the limit on the number of simultaneous requests that will be served. With pm = dynamic the pool grows from pm.start_servers up to that cap; with pm = static it is always that size; with ondemand workers start when requests arrive.

When all workers are busy, requests wait in the listen queue (the backlog defaults to 511 on Linux). The status page, enabled with pm.status_path, reports the listen queue, the max listen queue and max children reached, which is greater than zero if the pool ever hit its cap. If the pool is the bottleneck during your test, you will see the queue grow while CPU is not yet full.

A common sizing rule is: memory you can give PHP divided by the average memory of one worker. Measure the worker size under load rather than guessing, because it differs between a plain page and an admin report. The PHP manual does not give a formula, so treat the rule as a starting point.

How should you set up OPcache before a test?

OPcache keeps compiled scripts in shared memory, which saves PHP from recompiling them on each request. It is enabled by default (opcache.enable=1). Two limits matter for big applications: opcache.memory_consumption (128 MB by default) and opcache.max_accelerated_files (10,000 by default). If the cache is too small to hold the whole codebase, it stops helping and CPU rises. Also remember opcache.validate_timestamps, which defaults to on and checks files for changes every opcache.revalidate_freq seconds (2 by default); production setups often turn that off and reset the cache on deploy. Test with the setting you ship.

A restart of PHP-FPM empties the cache, so the first requests after a restart are slower than the rest. That is why you test warm and cold separately: cold shows what a deploy or restart feels like, warm shows steady state.

How do database connections and slow queries show up?

The PHP manual notes that with persistent connections every worker keeps its own connection, so 20 workers can mean 20 database connections, and it advises keeping the database’s maximum connections above the maximum number of web workers plus cron and admin use. For MySQL, running out of connections produces Too many connections errors, controlled by max_connections. Connection pools between PHP and the database (a proxy) are one fix; see optimising database connection pools for the general idea.

Turn on the slow query log before the test (slow_query_log = 1). MySQL’s default long_query_time is 10 seconds, far too coarse for a web page, so lower it to something like 1 or 2 seconds for the run. mysqldumpslow summarises the log afterwards, for example mysqldumpslow -s t -t 10 /var/log/mysql/slow.log for the ten queries with the most total time.

PHP-FPM has its own slow log: set request_slowlog_timeout (off by default) and slowlog to dump a PHP backtrace for requests that run longer than the timeout, which shows which line of code the worker was stuck on.

Do PHP sessions slow a load test?

They can. PHP’s default session handling locks the session data, so only one script operates on a session at any time (php.net, session_write_close). If your test logs in once and then fires parallel requests under the same session, they run one after another and you measure the lock, not your server. Two fixes: give each virtual user its own account and session (in JMeter, CSV data feeds a different login to each thread), and in your code call session_write_close() as soon as you have finished writing to the session so long requests do not hold the lock.

How do you build the load test in JMeter?

  1. Write down the goal first. For example, “the checkout stays under 2 seconds at the peak hour traffic we expect, with under 1 percent errors”. The scenario design guide covers workload models and goals.
  2. Install JMeter. It needs Java 8 or higher (jmeter.apache.org, October 2026). Download it from the Apache site.
  3. Add a Thread Group. Each thread is one virtual user. Set the number of threads, a ramp-up period and either a loop count or a duration. The ramp-up guide explains how to choose it.
  4. Add HTTP Request Defaults and HTTP Request samplers. Put the host in the defaults, then add one sampler per step of the user journey (home, search, product, add to cart, login).
  5. Add an HTTP Cookie Manager. PHP sessions ride on a cookie (PHPSESSID by default), and each JMeter thread keeps its own cookies, so every virtual user gets its own session.
  6. Add think time. A timer between steps stops the test from hitting the server faster than real users do. Take the pauses from your analytics.
  7. Add assertions. A PHP page can return HTTP 200 with a fatal error message in the body, so assert on a string that proves the page rendered.
  8. Run it without the GUI. The JMeter manual says GUI mode is for creating the script and the command line for load testing:
jmeter -n -t php-site.jmx -l results.jtl -e -o report

-n is non-GUI, -t the test plan, -l the results file, -e generates the HTML report at the end and -o the output folder, which must not exist or must be empty. The non-GUI mode guide walks through it.

Start with a small run (a handful of threads) to prove the script works, then step the load up: for example 10, 25, 50, 100 threads, holding each step long enough to see a steady state.

What should you watch on the server during the test?

Watch these side by side with JMeter’s results, because a slow response alone does not tell you where the time went:

  • PHP-FPM status: active and idle processes, listen queue, max children reached.
  • CPU, memory and swap on the web and database hosts.
  • Database Threads_connected against max_connections, and the slow query log.
  • The web server’s error log. With nginx in front of PHP-FPM, fastcgi_read_timeout defaults to 60 seconds between two reads from PHP, and a worker that is stuck longer than that produces a 504 for the visitor.

Line up the moment errors or latency start with the moment one of these hits its ceiling. That pairing is the finding.

How do you read the results?

Look at the response time percentiles (95th and 99th), not only the average, the error rate, and throughput as load rises. A healthy site shows throughput growing with load until the first ceiling, then flattening while response time climbs. The load at which that bend starts is your practical capacity. For the definitions, see what response time means and how many concurrent users a machine can run. Past that bend you are stress testing; load testing vs stress testing explains the difference.

When should you run the test from the cloud?

A single laptop limits how many virtual users JMeter can drive and where the traffic comes from. LoadFocus runs an existing JMeter .jmx file in the cloud from 25 regions, and you set virtual users, duration and ramp-up per run without editing the script. The free plan runs up to 10 virtual users, and paid plans scale from 250 up to 20,000 concurrent virtual users per test (loadfocus.com/jmeter-load-testing, October 2026). Make sure the test target allows the traffic: a staging copy of the site is the safe default.

Frequently Asked Questions

How do I load test a PHP website?

Record or script the main user journeys in a tool such as Apache JMeter, run them from separate machines against a staging copy, raise the load in steps, and watch response times, errors, the PHP-FPM status page and the database during each step.

What limits a PHP site under load?

Most often the PHP-FPM worker pool. PHP serves one request per worker process, and pm.max_children caps how many requests run at once. After that comes the database: connections, then slow queries.

What should I check first?

The PHP-FPM status page (queue and max children reached), the database connection count against max_connections, and OPcache size. These three are invisible until concurrency rises, then requests queue for a resource nobody was watching.

Should I test with a warm or cold cache?

Both. Warm shows steady state; cold, after a PHP-FPM restart or a deploy, shows what visitors feel at the worst moment. Reporting only the warm number produces a result you cannot reproduce after a deploy.

Can I load test a PHP site without JMeter?

Yes. Any HTTP load tool works, since the server only sees requests. JMeter is a common free choice and the same .jmx files run in the cloud on LoadFocus.

Sources

Chris

Head of Content at LoadFocus

Chris leads content and oversees development across the services this blog covers. The guides and tool comparisons here come out of the same decisions that shape what ships.

Weekly tips right in your inbox.

Don’t miss out on the latest testing tips and tricks, tools and news related to software testing.

Already +20,000 members in the list.

The Load Testing Platform with AI That Explains Your Results

Load test websites and APIs from 25 cloud regions, monitor page speed and uptime, and get AI analysis that explains your results in plain English.

Cloud load testing, website speed and API monitoring in one place.

Hi there! Can I share a cool thing I'm working on with you?

A software testing newsletter that sparks joy.

Our goal is to create helpful content for software developers and testers.

If you're not into it, you can unsubscribe instantly.