Performance

WordPress Load Testing with k6: How We Find Your Site's Limits

How we load test and stress test WordPress sites with k6: test types, realistic scripts, safe setup, reading results, and why it matters before big days.

Load testing shows how many visitors your WordPress site can handle before it slows down or fails, and which part breaks first. We use k6, an open-source tool from Grafana Labs, to simulate real visitors browsing, searching and checking out at rising traffic levels. The result is a number you can plan around, not a guess.

This guide explains the kinds of tests we run, how a k6 test is built, how to run one safely, and how to read the results.

Why load testing is worth doing

Most WordPress sites are never tested under load until real traffic does it for them. That usually happens on the worst possible day: a Black Friday sale, a product launch, a big email campaign or a mention on TV. A load test answers a few practical questions before that day:

  • How much traffic can we handle? Know your limit in visitors or orders per minute, and how much room you have above a normal busy day.
  • What breaks first? It might be the database, the PHP workers, a slow plugin, a third-party payment or shipping service, or the hosting plan itself.
  • Did the change help? After server tuning, a hosting move or a plugin cleanup, run the same test and compare. That is how you prove a fix worked.
  • Are we paying for the right hosting? Some sites need a bigger server. Many need better caching on the server they already have.

A fast page for one visitor, which is what tools like PageSpeed Insights measure, does not mean a fast site for a thousand visitors at once. Core Web Vitals and load testing answer different questions, and you need both. See Core Web Vitals for WordPress for the first one.

The types of test

The k6 documentation describes six test types. Each answers a different question:

TestWhat it doesQuestion it answers
SmokeRuns the script with very few usersDoes the test work, and is the site healthy at minimal load?
Average loadSimulates a normal busy dayDo we meet our speed targets under expected traffic?
StressGoes beyond normal trafficHow does the site behave above the expected level?
SpikeJumps to very high traffic suddenlyDo we survive a sudden rush, such as a sale opening?
SoakHolds normal load for hoursDo memory leaks, full disks or slow buildup appear over time?
BreakpointKeeps increasing load until failureWhere exactly is the limit?

We always start with a smoke test, as the k6 docs recommend. Then we run an average-load test, and only then a stress or spike test. Running a heavy test on a broken script wastes time and can hide real problems.

Building a realistic test

The most common load testing mistake is hammering the homepage. A cached homepage is the cheapest page on your site to serve, so the test looks great and tells you nothing. Real visitors do more expensive things.

Model real visitor journeys

We look at your analytics and build journeys that match what visitors actually do. A store might have three:

  • A browser who views the homepage, a category and two products.
  • A searcher who uses site search and opens a result.
  • A buyer who adds a product to the cart and reaches the checkout page.

Each journey gets a share of the traffic that matches your analytics. Pauses between pages, which k6 calls think time, make the pace realistic.

Include the pages that skip the cache

Page caching makes WordPress fast for anonymous visitors, but some requests always go to PHP and the database:

  • Carts and checkouts. WooCommerce sets cookies once something is in the cart, and most caches skip pages for those visitors.
  • Site search, because every search term is a new page.
  • Logged-in visitors, such as members, students or customers in their account area.
  • Admin-ajax and REST API requests from plugins.

These are where sites fail under load. k6 keeps cookies separately for each virtual user (VU), so a VU that adds a product to its cart stays a “cart visitor” for the rest of its journey, just like a real shopper.

A simple k6 script

Here is a short average-load test. It ramps up to 50 virtual users, holds for five minutes and ramps down. The thresholds turn it into a pass or fail check:

import http from 'k6/http';
import { check, sleep } from 'k6';

const BASE = __ENV.BASE_URL || 'https://staging.example.com';

export const options = {
  stages: [
    { duration: '2m', target: 50 }, // ramp up to normal busy traffic
    { duration: '5m', target: 50 }, // hold
    { duration: '2m', target: 0 },  // ramp down
  ],
  thresholds: {
    http_req_failed: ['rate<0.01'],    // fewer than 1% failed requests
    http_req_duration: ['p(95)<800'],  // 95% of requests under 800 ms
  },
};

export default function () {
  const pages = ['/', '/shop/', '/?s=chair', '/product/sample-product/'];
  for (const path of pages) {
    const res = http.get(`${BASE}${path}`);
    check(res, { 'status is 200': (r) => r.status === 200 });
    sleep(Math.random() * 3 + 2); // 2 to 5 seconds of think time
  }
}

You run it with k6 run -e BASE_URL=https://staging.example.com script.js. If a threshold fails, k6 exits with an error code, as its thresholds documentation explains. That makes the same script useful in an automated pipeline. See WordPress CI/CD with GitHub Actions.

A real test is longer than this. It has separate journeys, test products, data that changes per user and checks on the page content, not only the status code. The structure stays the same.

Users or requests per second

The script above controls the number of virtual users. Each one waits for a response before moving on, so a slow site gets fewer requests. That is realistic for a fixed group of visitors.

For a stress or spike test, we often control the arrival rate instead: new journeys start at a set pace no matter how slow the site gets, which is how real traffic behaves during a rush. k6 calls this an open model and provides executors such as ramping-arrival-rate for it.

Running tests safely

A load test is a controlled flood of traffic. Done carelessly, it can take down the site you are trying to protect.

  • Test a staging copy where you can. It should run on the same server type, with the same caching and the same CDN setup, or the numbers will not mean much. Our guide to safe WordPress updates with staging covers how to build one.
  • Agree on production tests with the host. Some hosts do not allow load testing without permission, and their firewall may block the test as an attack. We schedule production tests at quiet times, with the host in the loop.
  • Never create real orders or payments. Use test products and the payment gateway’s test mode, and stop journeys at the checkout page unless the test environment is fully isolated. Turn off real emails to customers.
  • Ramp up gradually and watch. Start small, increase in steps, and stop at once if real visitors are affected.
  • Warm the cache first, or test both. A test against an empty cache and a test against a warm one give different results. Both matter: after a deployment, your cache is empty.

Reading the results

k6 prints a summary when a test ends and can show a live web dashboard while it runs, enabled with the K6_WEB_DASHBOARD=true setting. We look at a few numbers first:

  • 95th percentile response time (p95). 95% of requests were faster than this. Averages hide the slow requests that real visitors notice. The p95 does not.
  • Error rate. Any rise in failed requests, timeouts or 5xx server errors is a warning sign.
  • Throughput. Requests per second the site actually served. When you add users but throughput stops growing, you have found a bottleneck.
  • The knee. The point where response times start to climb steeply. Your safe capacity sits comfortably below it.

k6 shows what visitors experience. To learn why, we watch the server at the same time:

  • CPU and memory on the web and database servers.
  • PHP-FPM. If its log says the server reached the pm.max_children setting, requests are waiting in a queue for a free PHP worker.
  • The database slow query log, which shows the queries that get slower under load.
  • Object cache hit rate, if you use Redis or Memcached.
  • Third-party calls, such as payment, shipping or tax services. These often become the slowest part of a checkout.

Matching a k6 slowdown to what the server was doing at that moment is how you find the real cause. We explain the server side in WordPress server tuning: PHP-FPM, OPcache and Redis, and the general diagnosis in why is my WordPress site slow.

What usually breaks first

Every site is different, which is why we test instead of guessing. These are common findings:

  • Too few PHP workers, or too many for the server’s memory.
  • Uncached pages that should be cached, or a cache that is cleared too often.
  • Slow database queries from a plugin, usually on search, filters or product listings.
  • No persistent object cache, so the same queries run again on every request.
  • Heavy admin-ajax calls from plugins that fire on every page view.
  • External services that slow down or rate-limit under load.

Each finding comes with a fix, and each fix gets tested again with the same script. The before and after numbers become part of the report.

Make load testing a habit

A load test is most useful before something changes: a big sale, a launch, a new plugin, a theme rebuild or a hosting move. Keep the scripts in version control next to the site’s code, and run the same test before and after each change. Over time, the results become a record of your site’s capacity. That record is a useful part of measuring technical debt.

Find your site’s limits before your customers do

Load and stress tests with k6 are part of our WordPress performance review, and we use them to measure every change in our server tuning work. Contact us and tell us about your next big day, and we will help you get ready for it.

Frequently asked questions

What is the difference between a load test and a stress test?
A load test checks how the site behaves at the traffic you expect, such as a normal busy day. A stress test pushes past that level to find where the site slows down or starts failing, and what breaks first.
Can a load test take my site down?
It can, which is why we usually test a staging copy on matching hardware, or test production at a quiet time with the host's agreement. Tests start small and ramp up, and they can be stopped at once.
Why use k6 for WordPress load testing?
k6 is an open-source load testing tool from Grafana Labs. Tests are written as JavaScript, so we can model real visitor journeys, set pass or fail limits, keep scripts in version control and run them again after every change.
How many visitors can my WordPress site handle?
It depends on your hosting, caching, plugins and what visitors do. Cached pages for anonymous visitors are cheap to serve. Carts, checkouts, searches and logged-in pages are not. A load test answers the question for your actual site.

Is your WordPress site hacked, slow or at legal risk?

Tell us what you're dealing with. We'll reply within one business day.