# Website Load Testing & Stress Test: Uptime, Speed, SSL (`pnda/website-load-test`) Actor

Load testing and stress test for your own website: up to 50 concurrent users for 5 minutes, p50/p90/p99 latency, error rate, throughput. Plus uptime, TTFB, SSL expiry, redirects and broken link checks, with a pass/fail verdict. No proxies, no fake traffic.

- **URL**: https://apify.com/pnda/website-load-test.md
- **Developed by:** [PNDA](https://apify.com/pnda) (community)
- **Categories:** Developer tools, SEO tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 url checkeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Website Load Testing & Stress Test: uptime, speed, SSL and broken links for your own website

**Website Load Testing** runs a real **load test / stress test** against **your own website** from the Apify cloud: up to **50 concurrent users for up to 5 minutes**, with **p50 / p90 / p99 latency, error rate, status codes and throughput** for every URL. In the same run it does a full **website health check**: **uptime**, **TTFB** and timing breakdown (DNS, connect, TLS), **SSL certificate expiry**, **redirect chain**, compression, security headers and an optional **broken link checker**. A summary row gives a clear **pass / fail verdict** against your thresholds, so you can use it in CI or as a scheduled **uptime monitor**.

> **Only test websites you own or are explicitly allowed to test.** The Actor asks you to confirm it on every run, refuses well-known third-party websites, and identifies every request with the User-Agent `pnda-load-test (+https://apify.com/pnda/website-load-test)`. It is **not a traffic generator**: no proxies, no IP rotation, no fake browsers, no fake visits. Load testing someone else's website without permission can be illegal.

### What you get

| | Website Load Testing (this Actor) | Typical "traffic generator" | Online speed test |
|---|---|---|---|
| Purpose | Measure how your site holds under load | Inflate visit counters | One page, one visitor |
| Latency percentiles (p50 / p90 / p99) | **Yes, per URL** | No | No |
| Error rate, status codes, requests per second | **Yes** | No | No |
| TTFB, DNS / connect / TLS timing | **Yes** | No | Partly |
| SSL expiry, redirects, broken links | **Yes** | No | Rarely |
| Pass / fail verdict for CI and monitoring | **Yes** | No | No |
| Proxies, fake user agents | **Never** | Residential proxies | – |

### Use cases

- **Load testing before a launch, a sale or a TV spot**: check that your shop answers fast with 10, 25 or 50 users hitting it at the same time.
- **Website stress test after a server or plugin change**: compare p90 latency and error rate before and after.
- **Uptime monitor**: schedule a check-only run every 5, 15 or 60 minutes and get alerted (Apify integrations: Slack, email, webhooks) when the verdict is `fail`.
- **SSL checker**: get warned weeks before your SSL certificate expires.
- **Broken link checker**: find 404 links, images and scripts on your key pages.
- **Website speed test from the server side**: TTFB and total load time percentiles, with DNS / TCP / TLS breakdown.

### How it works

1. **Health check of every URL** (`samplesPerUrl` timed requests, each on a fresh connection): status, uptime, redirects (followed up to 10), TTFB and total time percentiles, DNS / connect / TLS handshake time, page size, compression, cache headers, server, HTTP version, security headers, SSL certificate (issuer, validity, days left).
2. **Load test** (optional): `concurrency` virtual users send requests one after the other with keep-alive connections, spread over your URLs (round robin), ramped up over `rampUpSecs`. The test ends at `durationSecs` or `maxRequests`. Two **safety brakes**, measured on the last 100 requests: the test stops as soon as more than `stopOnErrorRatePercent` of them fail (5xx or no answer), so a site that is going down is not loaded further, and as soon as your firewall or CDN blocks most of them (403 / 429), with `stoppedBy: "firewall-block"`.
3. **Broken link check** (optional): every link, image, script and stylesheet of each HTML page, checked with HEAD and a GET fallback.
4. **Summary**: one row with the verdict, the failed checks and the warnings.

Requests are plain HTTP(S) from one Apify server (no browser), so the load test measures your **server and CDN**, not the browser rendering (Core Web Vitals such as LCP or CLS are not measured).

#### Safety limits

- Maximum **50 concurrent users**, **5 minutes** and **20,000 requests** per run.
- All URLs of a run must belong to **one website** (same domain and its subdomains).
- **Refused**: well-known third-party domains (Google, Facebook, Amazon, Apify, Cloudflare, Microsoft, Apple...), government, military and education domains, IP addresses, private and internal addresses.
- No proxies, no IP rotation, no user-agent spoofing.

### Input

```json
{
  "urls": ["https://www.my-shop.com/", "https://www.my-shop.com/products/best-seller"],
  "confirmOwnership": true,
  "samplesPerUrl": 5,
  "checkBrokenLinks": true,
  "maxLinksPerPage": 100,
  "loadTest": true,
  "concurrency": 20,
  "durationSecs": 60,
  "maxRequests": 5000,
  "rampUpSecs": 10,
  "method": "GET",
  "maxP90Ms": 2000,
  "maxErrorRatePercent": 1,
  "maxTtfbMs": 800,
  "minSslDaysLeft": 14
}
```

For an **uptime monitor**, keep `loadTest` off and schedule the Actor: each run costs one URL check per URL.

### Output

Every row has a `type`. Download as JSON, CSV or Excel, or read the `SUMMARY` record of the key-value store.

**`check`** (one per URL):

```json
{
  "type": "check",
  "url": "http://my-shop.com/",
  "finalUrl": "https://www.my-shop.com/",
  "up": true,
  "status": 200,
  "redirects": [{ "url": "http://my-shop.com/", "status": 301, "location": "https://www.my-shop.com/" }],
  "httpsRedirect": true,
  "ttfb": { "count": 5, "p50Ms": 182.4, "p90Ms": 240.1, "p99Ms": 240.1, "minMs": 170.2, "maxMs": 240.1, "meanMs": 195.3 },
  "total": { "count": 5, "p50Ms": 301.7, "p90Ms": 355.0, "p99Ms": 355.0 },
  "dnsMs": 4.1, "connectMs": 12.3, "tlsHandshakeMs": 25.8,
  "pageSizeBytes": 48211, "compression": "br", "httpVersion": "1.1",
  "ssl": { "valid": true, "issuer": "Let's Encrypt / R11", "validTo": "2026-12-30T10:11:12.000Z", "daysLeft": 82, "protocol": "TLSv1.3" },
  "securityHeaders": { "strictTransportSecurity": true, "contentSecurityPolicy": false, "xContentTypeOptions": true, "xFrameOptions": true, "referrerPolicy": true }
}
```

**`load-test`** (one per URL tested):

```json
{
  "type": "load-test",
  "url": "https://www.my-shop.com/",
  "requests": 2410, "successful": 2408, "errors": 2, "errorRatePercent": 0.08,
  "statusCodes": { "200": 2408, "502": 2 },
  "latency": { "p50Ms": 210.5, "p90Ms": 402.3, "p95Ms": 512.0, "p99Ms": 901.7, "maxMs": 1820.4 },
  "ttfb": { "p50Ms": 180.2, "p90Ms": 350.9, "p99Ms": 850.1 },
  "requestsPerSecond": 40.17,
  "bytesReceived": 116209152
}
```

**`load-test-timeline`**: requests, errors, median latency and active users for every second of the test (to chart the ramp-up).

**`links`** (one per page): links found and checked, and the list of broken ones with their status.

**`summary`**:

```json
{
  "type": "summary",
  "site": "my-shop.com",
  "verdict": "fail",
  "failures": ["load test: p90 latency 2310 ms > 2000 ms"],
  "warnings": ["2 broken link(s) found"],
  "loadTest": { "requests": 4820, "requestsPerSecond": 80.3, "errorRatePercent": 0.1, "p50Ms": 420, "p90Ms": 2310, "p99Ms": 3900, "durationSecs": 60, "concurrency": 20, "stoppedBy": "duration" },
  "sslDaysLeft": 82,
  "brokenLinks": 2,
  "message": "FAIL: 2/2 URL(s) up, load test 4820 requests at 80.3 req/s, p90 2310 ms, 0.1% errors, 2 broken link(s), 1 failed check(s)."
}
```

### Pricing (pay per event, results only)

| Event | Price |
|---|---|
| URL checked (health check: uptime, timings, SSL, redirects) | **$0.003** ($3 per 1,000) |
| Load test, per started minute | **$0.05** |
| Link checked (broken link checker) | **$0.0005** ($0.50 per 1,000) |

Examples: an uptime monitor on 3 URLs every 15 minutes costs about $0.86 per day. A 1-minute load test with 50 users on 2 pages costs $0.056. A 5-minute stress test costs $0.25.

The Actor checks your **maximum cost per run** before every paid step: the load test is shortened to fit it, and a run that cannot afford a single URL check stops with a `budget-too-low` row, free.

**Works with a free Apify account too.** Free-plan accounts can use every feature at the same per-result prices, paid from their monthly Apify credit. The only limits are your credit and your maximum cost per run.

### Run status

Runs end **SUCCEEDED** with an explicit row and no charge when the input is invalid (`invalid-input`: missing ownership confirmation, third-party domain, two different websites...), when your maximum cost per run is too low (`budget-too-low`) or when the free limit is reached (`free-plan-limit`). A failing website is a result (`up: false`, verdict `fail`), not a failed run.

### FAQ

**Is this a traffic generator?** No. It sends identified test requests to measure performance. It does not simulate visitors, does not run JavaScript analytics tags and does not use proxies, so it will not (and is not meant to) increase your visitor counts.

**Why only 50 concurrent users?** The Actor is built to validate a small or medium website safely from one server. For tests with thousands of users from several regions, use a dedicated load testing platform.

**My site is behind Cloudflare or a WAF.** The test traffic comes from Apify cloud IPs with an honest User-Agent. Many CDNs (Vercel, Cloudflare, Sucuri...) start answering 403 or 429 to a burst of requests from one IP. The Actor detects it, stops the test (`stoppedBy: "firewall-block"`) and says so in the verdict. Allow-list the User-Agent `pnda-load-test` (or the test path) during the test to measure your server rather than your firewall.

**Can I monitor uptime on a schedule?** Yes: create a Task with `loadTest` off, add a Schedule, and an integration (Slack, email, webhook) on runs whose `SUMMARY.verdict` is `fail`.

### Related actors

- [Google Search Results Scraper](https://apify.com/pnda/google-search-scraper): check where your pages rank on Google after a speed fix.
- [Semrush Keyword & SEO Data](https://apify.com/pnda/semrush-keyword): keyword volumes, domain overview and backlinks for your website.

# Actor input Schema

## `urls` (type: `array`):

1 to 50 pages of ONE website you own (home page, product page, API endpoint...). All URLs must be on the same domain or its subdomains. Each URL gets a health check; the load test spreads its requests over all of them.

## `confirmOwnership` (type: `boolean`):

Required. Only test websites you own or are explicitly allowed to test. Load testing a third-party website without permission can be illegal and is against the Apify terms. Requests are identified with the User-Agent 'pnda-load-test (+https://apify.com/pnda/website-load-test)'.

## `samplesPerUrl` (type: `integer`):

How many timed requests, each on a fresh connection, measure DNS, connect, TLS, TTFB and total time of every URL. Percentiles are computed over these samples.

## `checkBrokenLinks` (type: `boolean`):

Check every link, image, script and stylesheet of each HTML page (HEAD, GET fallback). $0.50 per 1,000 links checked.

## `maxLinksPerPage` (type: `integer`):

Links checked per page, in page order.

## `loadTest` (type: `boolean`):

Send real concurrent traffic to your URLs and measure latency percentiles, error rate and throughput. $0.05 per started minute of load test.

## `concurrency` (type: `integer`):

Virtual users sending requests one after the other (keep-alive connections). Maximum 50.

## `durationSecs` (type: `integer`):

Length of the load test. Maximum 300 seconds (5 minutes).

## `maxRequests` (type: `integer`):

The test stops at this number of requests even before the duration. Maximum 20,000.

## `rampUpSecs` (type: `integer`):

Users are added linearly over this time, instead of all at once.

## `method` (type: `string`):

GET downloads the full response (realistic). HEAD only asks for headers (lighter for your bandwidth).

## `requestTimeoutSecs` (type: `integer`):

A request without a full answer after this time counts as an error.

## `stopOnErrorRatePercent` (type: `integer`):

Safety brake, measured on the last 100 requests: the test stops as soon as more than this share of them fail (5xx or no answer), so a site that is going down is not loaded further. The test also stops when your firewall or CDN blocks most requests (403 / 429).

## `maxP90Ms` (type: `integer`):

Load test threshold: 90% of requests must complete faster than this.

## `maxErrorRatePercent` (type: `integer`):

Load test threshold: share of requests answered 4xx / 5xx or not answered.

## `maxTtfbMs` (type: `integer`):

Health check threshold: median time to first byte of each URL.

## `minSslDaysLeft` (type: `integer`):

Health check threshold: days before the certificate expires.

## Actor input object example

```json
{
  "urls": [
    "https://www.your-website.com/"
  ],
  "confirmOwnership": false,
  "samplesPerUrl": 5,
  "checkBrokenLinks": false,
  "maxLinksPerPage": 100,
  "loadTest": false,
  "concurrency": 10,
  "durationSecs": 30,
  "maxRequests": 5000,
  "rampUpSecs": 5,
  "method": "GET",
  "requestTimeoutSecs": 30,
  "stopOnErrorRatePercent": 50,
  "maxP90Ms": 2000,
  "maxErrorRatePercent": 1,
  "maxTtfbMs": 800,
  "minSslDaysLeft": 14
}
```

# Actor output Schema

## `dataset` (type: `string`):

All rows (JSON, CSV, Excel). Filter on `type`: check, load-test, load-test-timeline, links, summary.

## `summary` (type: `string`):

The summary row as one JSON record.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "https://www.your-website.com/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("pnda/website-load-test").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "urls": ["https://www.your-website.com/"] }

# Run the Actor and wait for it to finish
run = client.actor("pnda/website-load-test").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    "https://www.your-website.com/"
  ]
}' |
apify call pnda/website-load-test --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,pnda/website-load-test"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/FHIuQMzmeCfYX4c4W/builds/XR5xxEKJRbmP7nUsJ/openapi.json
