Getting started

From a fresh purchase to your first benchmark, plus a reference for every campaign field.

On this page
  1. Install & activate
  2. Add a worker (same machine)
  3. Add a worker (different machine)
  4. Build a campaign
  5. Campaign field reference
  6. Reading the dashboard & telemetry grid
  7. Common pitfalls
  8. Troubleshooting

1. Install & activate

  1. Sign in at the customer portal and click Admin installer (Windows).
  2. Run BenchStress-Setup-x.y.z.exe. Windows SmartScreen will warn the first time (we're in the process of getting an Authenticode cert) — click More infoRun anyway. Walk through the wizard.
  3. Launch BenchStress from the Start Menu. On first launch you'll see a sign-in dialog asking for your purchase email and a 6-digit code.
  4. Enter your purchase email, click Send code. Check your inbox (subject: Your BenchStress activation code), enter the code in the dialog.
  5. The dashboard loads. The bundled Server (the SignalR broker that workers connect to) auto-starts on localhost:5050. You'll see "Server not connected" for ~2 seconds while it spins up, then it goes connected.
Heads up: Admin's title bar shows the running version (e.g. BenchStress v1.0.0). Same version is reported to the cloud and shown next to your portal downloads — useful for support questions.

2. Add a worker (same machine)

  1. From the portal, download Worker (Windows x64). Extract the .zip anywhere — desktop is fine.
  2. Double-click BenchStress.Worker.exe. A console window opens.
  3. It asks for your purchase email + 6-digit code (same flow as Admin). Enter both.
  4. After activation, the worker queries the cloud for your registered Admin sessions, finds the one running on the same box, and auto-connects to localhost:5050.
  5. Within a few seconds the worker shows up in your Admin's Dashboard tab with state Connected, and in your portal under Workers with a green status dot.

3. Add a worker (different machine)

For real distributed load testing, run workers on separate boxes. Each worker can drive 10K–50K simulated browsers depending on hardware.

  1. On the Admin machine, click + Add Worker on the dashboard. The dialog shows every detected LAN IP on this box. Pick the network the worker boxes can reach (usually wired Ethernet, not Hyper-V/VPN/WSL adapters), click Save. This is what gets reported to the cloud as your Admin's address.
  2. Run the firewall command shown in Step 3 of that dialog (one-time, in PowerShell as admin) so inbound TCP 5050 is allowed.
  3. On each worker box: download the appropriate worker bundle (Windows zip or Linux .tar.gz) from the portal. No login required — wget works fine on headless Linux:
    wget https://benchstress.com/downloads/BenchStress.Worker.linux-x64.tar.gz
    tar xzf BenchStress.Worker.linux-x64.tar.gz
    chmod +x BenchStress.Worker
    ./BenchStress.Worker
  4. The worker prompts for email + code. After activation it queries the cloud, finds your Admin (auto-picks if there's only one, shows a console menu if multiple), and connects.
  5. It saves the chosen Admin URL into worker.json next to the exe so subsequent launches are silent. Within ~60 seconds the worker shows up in your Admin's Dashboard.
Admin moved hosts? Workers re-check the cloud every 5 minutes. If your saved Admin is gone or offline AND a different one is online, they self-restart and switch automatically — you don't have to touch worker.json.

4. Build a campaign

  1. Click Campaigns in the sidebar.
  2. Click New campaign. Fill in the form (full reference below).
  3. Tick the workers you want this campaign to run on (you can also exclude specific workers if some are busy).
  4. Click Save & Start. The campaign begins ramping immediately. Switch to the Telemetry tab to watch live.
  5. Click Stop on the Telemetry tab to end the campaign. It can be restarted at any time from Campaigns.

5. Campaign field reference

Name

Free-form label so you can find this campaign later. Doesn't affect traffic.

Browser profile

A preset that injects realistic User-Agent, Accept, Accept-Language, Accept-Encoding, and Sec-Ch-Ua-* client hints for the chosen browser. Pick the browser your real traffic looks like — servers, CDNs, and bot-protection middleware all branch on these headers.

Custom headers (below) layer on top and override these.

Target URL

Full HTTP/HTTPS URL to hit. Path and query string are sent verbatim.

Examples:

https://api.example.com/v1/health
https://shop.example.com/products?category=shoes&sort=popular
https://staging.example.com/api/users/42
http://internal-svc.lan:8080/metrics
Method

GET, POST, PUT, PATCH, DELETE, HEAD. Pair non-GET with a Body and a matching Content-Type header.

Host header override (optional)

Sends a different Host: header than the URL's hostname. The URL still controls where the TCP packets go (DNS resolution); the Host header controls which vhost the server routes to.

Use it to:

Examples (for each, the URL goes in Target URL, the value below goes in this field):

www.example.com
shop.example.com
api-staging.example.com
prod-origin.example.com:8443

Concrete pairing — bypass Cloudflare and hit your origin directly:

Target URL:           https://203.0.113.5/api/health
Host header override: api.example.com

Empty = use the URL's hostname (normal behavior).

Body (optional)

Raw request body, sent verbatim. Plain text, JSON, form-encoded, anything your endpoint accepts. Pair with a Content-Type custom header so the server parses it.

Example — JSON login payload:

{"email":"[email protected]","password":"hunter2","remember":true}

Example — form-encoded (also set Content-Type: application/x-www-form-urlencoded in custom headers):

username=alice&password=hunter2&next=%2Fdashboard

Example — GraphQL query (with Content-Type: application/json):

{"query":"query { products(first: 20) { id name price } }"}

Empty for GET/HEAD.

Custom headers (one per line, Key: Value)

Layered on top of the Browser profile headers. Your benchmark is only as realistic as the headers it sends — missing Cookie:/Authorization: hits the unauthenticated path and underestimates real load by a lot.

Common pattern — authenticated JSON API call:

Content-Type: application/json
Accept: application/json
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

Common pattern — logged-in browser session:

Cookie: session=abc123def456; csrftoken=xyz789
Accept-Language: en-US,en;q=0.9
Referer: https://shop.example.com/
X-Requested-With: XMLHttpRequest

Common pattern — multi-tenant SaaS, route to a specific tenant:

X-Tenant-Id: acme
X-Api-Key: live_pk_8a91f...
Authorization: Bearer eyJhbGciOi...

Common pattern — bypass CDN/edge cache so your origin actually sees the load:

Cache-Control: no-cache
Pragma: no-cache
CF-Connecting-IP: 198.51.100.42

Common pattern — mobile client simulation:

User-Agent: MyApp/2.4.1 (iPhone; iOS 17.5; Build/21F79)
Accept: application/json
X-Client-Version: 2.4.1
X-Device-Id: 3F2A8B1C-...

Tip: open your real site in Chrome DevTools → Network tab → right-click a request → Copy as cURL. The headers in that command line are exactly what you should mirror here for a representative benchmark.

Clients per worker

How many simulated browsers each worker keeps alive simultaneously. 20 = each worker holds 20 concurrent clients. With 3 workers, 60 concurrent clients total.

High-end limit per worker is roughly 50,000 on beefy hardware. Practical sweet spot for most targets is 1,000–10,000.

Ramp-up per second

How many additional clients each worker activates per second until it hits Clients per worker. 2 with 20 clients = full ramp in 10 seconds.

Higher = more aggressive cold-start. Lower = gentler, easier to spot exactly when the system tipped over.

Global RPS cap

Hard ceiling on total requests/second across all workers in this campaign. 10000 = workers throttle themselves so the sum never exceeds 10K RPS.

Set this when you have an SLO target you want to sustain rather than overrun. Leave high (or empty) when you want to find the breaking point.

Duration (s)

How long the campaign runs before auto-stopping. Untick for an open-ended run you stop manually.

Think-time (ms)

Pause between requests on each individual client, simulating user reading the page. 100ms means each client waits 100ms after a request finishes before sending the next one.

Set to 0 for max-throughput stress (each client fires the next request immediately on response). Higher values produce more realistic per-user load and let you sustain more concurrent clients without saturating.

HTTP/2

Toggles the wire protocol.

Match the protocol your real users actually use. CDN/Cloudflare-fronted? Almost certainly HTTP/2 on. Legacy on-prem behind an old load balancer? Maybe HTTP/1.1.

Keep-Alive

Turn off only when you specifically suspect the handshake/TLS layer is your bottleneck. Otherwise leave on.

Auth (optional)

Convenience shortcut for Authorization: headers. Equivalent to setting it manually in custom headers, just less typo-prone.

Examples:

Bearer:  eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0In0.xxx
Basic:   admin:hunter2
Basic:   apikey:                                  (some APIs use empty password)

Bearer is what most modern API tokens / OAuth use. Basic is mostly legacy admin endpoints. If you need a non-standard scheme (Token, Hawk, etc.), put it in Custom headers directly:

Authorization: Token a1b2c3d4e5f6...

6. Reading the dashboard & telemetry grid

Dashboard summary tiles

Telemetry top bar

Per-client telemetry grid

Each cell is one simulated client on one worker. Color reflects current state:

Idle — not started yet
Connecting — TCP/TLS handshake in flight
Connected — idle, ready to send
Transmitting — request in flight (alternates with blue for visible animation)
Transmitting (alt frame)
Done — request completed successfully (cell briefly flashes green between requests)
Error — last request failed (non-2xx or transport error)

Cells flicker between Transmitting (cyan/blue) and Connected/Done as requests cycle. A solid-green column with no flicker for a long time means clients connected but aren't completing requests — check that worker's network reachability to the target.

7. Common pitfalls

8. Troubleshooting

Admin window doesn't appear

Logs are at %LocalAppData%\BenchStress\logs\admin-YYYYMMDD.log. Open in Notepad — the last lines will explain. The dashboard's Reconnect button safely retries the SignalR handshake.

Worker prints "No Admin sessions are registered to this license yet"

Means the cloud doesn't see any Admin running for your license. Make sure your Admin app is open and has hit at least one heartbeat (~60 seconds after launch). Then re-launch the worker.

Worker connects to localhost:5050 but Admin is on a different machine

Discovery hasn't run for some reason. Delete the worker.json next to BenchStress.Worker.exe and re-launch — that forces fresh discovery. Or pass --server http://<admin-lan-ip>:5050 on the command line.

I revoked a machine in the portal and now its app shows nothing

Wait ~60 seconds, then close and reopen the app on that machine. It detects the revoke, prompts for a fresh activation code, and reconnects.

Two rows for the same hostname in the dashboard

Each Worker install gets a fresh worker-id.txt. Reinstalling to a new folder creates a new id, so the old one shows as Disconnected for ~5 minutes before auto-pruning.

Logs flooded with HttpRequestException: Only one usage of each socket address

That's Windows ephemeral port exhaustion (WSAEADDRINUSE / error 10048), not a BenchStress bug. At high outbound rates the OS runs out of source ports faster than they recycle from TIME_WAIT. Default range is only ~16K ports with a ~240s recycle window.

Fix on the worker box (PowerShell as Administrator):

netsh int ipv4 set dynamicport tcp start=10000 num=55535
reg add "HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" /v TcpTimedWaitDelay /t REG_DWORD /d 30 /f

Reboot after the registry tweak. That gives you ~55K ports recycling every 30s — about 1,800 fresh outbound TCP per second sustained on a single machine.

Better long-term: spread the load across more worker boxes. Each worker has its own port pool.

Can I include a port in the Target URL?

Yes — standard URI syntax works. Examples:

http://10.0.0.5:8080/health
https://api.staging.example.com:8443/v2/orders
http://localhost:3000/test

The Host header override field is separate — only set that if you need a non-matching Host: header for vhost / SNI testing.

Still stuck? Email [email protected] from your purchase email and include the relevant log file.