Security & Responsible Load Testing

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

A load generator is a tool for sending a very large amount of traffic on purpose. Used against a system nobody agreed to test, it is indistinguishable from a denial-of-service attack; used carelessly against your own system, it can leak the credentials and personal data that flow through it. JMeter itself is also software with attack surface: it executes whatever a .jmx file tells it to, it can install a root certificate authority in your browser, and in a distributed run it accepts remote commands over the network. This page collects what a team has to settle before, during and after a load test so that the test is authorised, the data stays protected and JMeter does not become the weakest machine on the network.

It was written against Apache JMeter 5.6.3, the latest release at the time of writing, on Java 21 (5.6.x needs Java 8 or newer; this guide recommends 17 or newer, 21 preferred). The examples were run on a 5.6.3 installation with Java 21. The Plugins Manager referred to is 2.0. JMeter 6.0 is not released; where the development branch plans a change that matters here (Java 17 as the minimum, updated bundled libraries), the text says "planned per changes.xml on master". The JMeter facts come from the official security page, best practices, remote testing chapter, component reference and properties reference. Authorisation and coordination are not covered by the JMeter manual; that part is general practice for responsible testing, and the only external rule quoted is a cloud provider’s published testing policy.

Everything on this page is defensive. It describes how to test systems you own or are authorised to test, and how to protect the test and its data; it does not describe how to evade detection or overload systems you are not responsible for.

Where the risks are

The picture below follows the Bookshelf test from the plan file to the report and marks where something sensitive is stored or exposed. Each box is a section of this page.

flowchart LR subgraph Author["Authoring machine"] REC["HTTP(S) Test Script Recorder - root CA in the browser"] JMX["bookshelf-load.jmx - must hold no secrets"] BSH["BeanShell server - off by default"] end subgraph Inputs["Run inputs"] CSV["users.csv - test accounts only"] SEC["secrets.properties or CI secret variables"] end subgraph Gen["Load generators"] CTRL["Controller"] NODE["Remote servers - RMI over SSL"] end SUT["Bookshelf API - authorised target"] OUT["JTL, logs, dashboard - may hold personal data"] REC --> JMX JMX --> CTRL CSV --> CTRL SEC --> CTRL CTRL -- "RMI + SSL" --> NODE NODE -- "load" --> SUT CTRL -- "load" --> SUT NODE -- "samples" --> CTRL CTRL --> OUT
Asset Risk Section

Permission to send load

Unauthorised traffic looks like an attack; shared infrastructure suffers

Authorisation and rules of engagement

.jmx files

Passwords stored in clear; untrusted plans can run arbitrary code

Keeping secrets out of .jmx files, Trusting .jmx files

Results and responses

Tokens, personal data and request bodies written to disk and CI artefacts

Personal data in results and responses

Recorder root CA

Anyone with the keystore can impersonate any site to the browser that trusts it

The recorder root CA

Distributed nodes

Remote execution agents on the network

RMI over SSL for distributed runs

BeanShell server

Unauthenticated remote shell into JMeter and the host

The BeanShell server

JMeter, Java and plugins

Known vulnerabilities in old versions and in third-party jars

Keeping JMeter, Java and plugins updated, Judging third-party plugins

Authorisation and rules of engagement

Only load-test systems you own or are explicitly authorised to test. A load test that saturates a server is, from the receiving side, a denial-of-service event: the same symptoms, the same alarms, and potentially the same consequences for other customers of shared infrastructure. Owning the application is not enough when it runs on someone else’s infrastructure: the hosting provider, the CDN, the API gateway, the database service and every third-party API that the journey touches are also on the receiving end.

Before the first non-smoke run, get written agreement on the points below. A short document or ticket is enough; what matters is that the people who could be paged by the test know it is coming.

Item What to agree

Owner and approver

Who owns the system under test and who approved this test, by name.

Target

Exact host names, IP ranges and environments. For Bookshelf: localhost:8080 for development, a named staging environment for the scheduled runs. Production only with an explicit, separate approval.

Source

Which load generators send the traffic and from which addresses, so operations and security teams can recognise it.

Window

Start, end and time zone. Avoid windows that overlap backups, batch jobs or other teams' tests.

Peak load

The highest arrival rate or number of threads, in numbers. For the Bookshelf load profile: 50 threads with a 3 s think time and about 0.2 s response time, so about 50 / 3.2 ≈ 15.6 requests per second at full load; a stress profile states its ceiling the same way.

Stop conditions

Error rate, latency or resource thresholds at which the test is stopped, and who may stop it.

Kill switch

How to stop it: shutdown.sh (graceful) or stoptest.sh (immediate) on the controller, which send a command to the UDP port JMeter listens on (4445 by default, loopback only). Agree in advance who runs it and from where.

Contacts

On-call engineers for the system, the load-test operator, and the people at each provider who must be told.

Data

Which accounts and data the test uses (test accounts only), and what happens to the results afterwards.

Coordinate with providers. Tell everyone whose infrastructure carries the traffic:

  • Your own operations and security teams. Intrusion detection, web application firewalls and rate limiters react to load tests. Agree whether the generators are allow-listed for the test or whether the test should go through the same protection as real users (often the more honest measurement).

  • Cloud and hosting providers. Each publishes its own rules for load and stress tests. For example, the Amazon EC2 Testing Policy treats high-volume tests of legitimate traffic towards a target that can handle it as stress tests, explicitly prohibits volumetric network DDoS simulations from the EC2 platform, and refers DDoS simulations to a separate policy. Read the current policy of every provider involved, on both the generator side and the target side, before the test.

  • CDNs, API gateways and SaaS front ends. Their protection may block the generators or, worse, let the test through and bill it.

  • Third-party APIs called by the system under test. A payment, email, SMS or identity provider should not receive load-test traffic. Point the system under test at a sandbox or a stub for the test, and check with the provider before sending any volume even to a sandbox.

Make the traffic recognisable. Add an identifying header with an HTTP Header Manager at the Test Plan level (Add → Config Element → HTTP Header Manager), so that logs and dashboards on the server side can separate test traffic from real traffic, and give the generators stable, documented addresses. A header is a label, not a security control: anyone can send it, so never let the system under test relax authentication or rate limits because of it.

<HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="Identify load-test traffic">
  <collectionProp name="HeaderManager.headers">
    <elementProp name="" elementType="Header">
      <stringProp name="Header.name">X-Load-Test</stringProp>
      <stringProp name="Header.value">bookshelf-load ${__P(run_id,local)}</stringProp>
    </elementProp>
  </collectionProp>
</HeaderManager>
<hashTree/>

Run with -Jrun_id=2026-10-14-staging to stamp every request with the run that sent it. The header name is a convention of this guide, not something JMeter or the server requires.

Start small. Run the smoke profile (1 thread) first, then a fraction of the agreed peak, and watch the system and the error rate before going higher. A plan that is wrong (a missing think time, an unbounded loop, a token extractor that fails so every request is retried) produces far more traffic than intended, and the first minute of a small run reveals it. The page on running tests in CLI mode shows how to read the live summariser for this.

Keeping secrets out of .jmx files

A .jmx file is an XML document that travels: it is committed to Git, attached to tickets, copied to every load generator and uploaded to CI. Anything typed into it is in all of those places. The component reference marks every password field that is saved as typed with the same sentence, "stored unencrypted in the test plan". In 5.6.3 that applies to:

Element Field stored unencrypted

HTTP Request, HTTP Request Defaults

Proxy server password

HTTP Authorization Manager

Password of each row (Basic, Digest, Kerberos)

JDBC Connection Configuration

Password

JMS Publisher, JMS Subscriber

Password

SMTP Sampler, Mail Reader Sampler

Login password

LDAP Request, LDAP Extended Request

Password (bind and login)

TCP Sampler

Password (not used by the default implementation)

Login Config Element

Default password

The same applies to anything else stored as plain text: an API key in a Header Manager, a client secret in a request body, a token pasted into a User Defined Variable. Recorded plans add one more case: the recorder always strips the Cookie and Authorization headers (proxy.headers.remove), but it keeps request bodies, so recording a login with a real password writes that password into the login sampler. Record with test accounts only, and replace the recorded values with variables as described on the recording page.

Where secrets should come from

The plan holds only references, such as ${adminPassword}; the values arrive at run time. The options, from the most to the least suitable for a shared or CI environment:

Source How the plan reads it Exposure

A properties file passed with -q, readable only by the user running JMeter

${__P(admin_password,)}

The file on the generator. jmeter.log records the file name, not the values.

An environment variable set by the CI system’s secret store

${__groovy(System.getenv('BOOKSHELF_API_KEY') ?: '')}

The process environment. CI systems mask registered secrets in their logs.

A CSV file of test accounts outside version control

CSV Data Set Config variables such as username and password

The file on every generator; acceptable for placeholder or test-only accounts.

-J on the command line

${__P(admin_password,)}

Visible to other users in the process list and saved in shell history. Avoid for real secrets.

The Bookshelf plan defines its secrets once, in a User Defined Variables element at the top of the Test Plan, so that every sampler refers to variables and the sources are visible in one place:

  1. Select the Test Plan and choose Add → Config Element → User Defined Variables. Name it Secrets (from environment and properties).

  2. Add a row apiKey with the value ${__groovy(System.getenv('BOOKSHELF_API_KEY') ?: '')}.

  3. Add a row adminPassword with the value ${__P(admin_password,)}.

  4. Use ${apiKey} and ${adminPassword} in Header Managers, Authorization Managers and request bodies.

<Arguments guiclass="ArgumentsPanel" testclass="Arguments" testname="Secrets (from environment and properties)">
  <collectionProp name="Arguments.arguments">
    <elementProp name="apiKey" elementType="Argument">
      <stringProp name="Argument.name">apiKey</stringProp>
      <stringProp name="Argument.value">${__groovy(System.getenv('BOOKSHELF_API_KEY') ?: '')}</stringProp>
      <stringProp name="Argument.metadata">=</stringProp>
    </elementProp>
    <elementProp name="adminPassword" elementType="Argument">
      <stringProp name="Argument.name">adminPassword</stringProp>
      <stringProp name="Argument.value">${__P(admin_password,)}</stringProp>
      <stringProp name="Argument.metadata">=</stringProp>
    </elementProp>
  </collectionProp>
</Arguments>
<hashTree/>

The secrets file holds a placeholder here; in a real setup it is created by the deployment of the load generator and never committed:

# secrets.properties - chmod 600, not in version control
admin_password=placeholder-admin-password

The run below uses secrets-demo.jmx: the element above plus one Debug Sampler, which makes the values visible for the demonstration in the next section. A real run uses bookshelf-load.jmx the same way.

chmod 600 secrets.properties
BOOKSHELF_API_KEY=placeholder-api-key \
  jmeter -n -t secrets-demo.jmx -q secrets.properties -l a.jtl
Creating summariser <summary>
Created the tree successfully using secrets-demo.jmx
Starting standalone test @ 2026 Oct 14 13:32:08 CEST (1791977528509)
Waiting for possible Shutdown/StopTestNow/HeapDump/ThreadDump message on port 4445
summary =      1 in 00:00:00 =   22.2/s Avg:     3 Min:     3 Max:     3 Err:     0 (0.00%)
Tidying up ...    @ 2026 Oct 14 13:32:09 CEST (1791977529365)
... end of run

In CI, the same command reads BOOKSHELF_API_KEY from the pipeline’s secret store and writes secrets.properties from another secret at the start of the job. The page on CI/CD with Maven, Docker and GitHub Actions later in this section shows the pipeline side.

__env is a plugin function. The __env function seen in many examples comes from the jpgc-functions plugin, not from core JMeter; the __groovy form above needs nothing beyond the bundled Groovy. For the per-element details of the Authorization Manager and Header Manager, see HTTP Sessions, Headers, Cookies & Authentication; for -q, -J and __P, see Properties, Variables & Functions.

Secrets in -D system properties. Keystores for client certificates are passed as system properties (javax.net.ssl.keyStorePassword). Expand them from an environment variable as in HTTP Requests, and remember that the expanded value is still visible in the process list while JMeter runs; -S with a system-properties file readable only by the user avoids that.

Distributed runs. Properties set on the controller with -J or -q are local to it; the remote servers evaluate __P against their own properties. -G sends properties to every server over RMI. Put the secrets file on every node, or send secrets with -G only when RMI runs over SSL (see below).

Checking a plan before committing it

A quick search catches the common mistakes. Every element with a password field writes a password property, so a non-empty value that is not a variable reference is suspect:

grep -nE '<stringProp name="[^"]*[Pp]assword[^"]*">[^$<][^<]*</stringProp>' *.jmx
grep -nE 'Bearer [A-Za-z0-9._-]{20,}|api[_-]?key' *.jmx

Both commands print nothing for a clean plan. A secret-scanning tool in the repository (for example in a pre-commit hook) gives broader coverage; the JMeter project does not ship one.

Personal data in results and responses

Load tests produce files: the JTL results file, jmeter.log, the HTML dashboard, optional XML files with response bodies, and anything a JSR223 element writes. Which of these contain personal or confidential data depends on what was saved.

Output Default content What can leak

CSV JTL (-l)

Times, labels, codes, messages, thread names; no bodies, headers or URLs

Sampler labels built from data (GET /api/books?query=${term}), assertion failure messages that quote the response, and any variable listed in sample_variables.

XML JTL, or a listener configured to save more

Same fields, plus whatever is switched on

Response bodies (jmeter.save.saveservice.response_data), request bodies (samplerData), request and response headers including Authorization (requestHeaders, responseHeaders), full URLs with query strings (url).

View Results Tree, Debug Sampler

Everything, including all variables

A Debug Sampler prints every JMeter variable, secrets read from the environment included, into its response.

Test Plan Functional Test Mode

Off

When on, every listener with a file saves the response data of every sample.

HTML dashboard

Aggregates, labels, error messages

Labels and error messages, as above.

jmeter.log

Start-up, warnings, errors

Stack traces and anything a script logs with log.info.

The defaults of the save properties are all false for bodies, headers, sampler data and URLs, which is the right starting point. The demonstration below checks the CSV file of the run above, then repeats the run with XML output and response data switched on.

grep -c placeholder a.jtl

BOOKSHELF_API_KEY=placeholder-api-key \
  jmeter -n -t secrets-demo.jmx -q secrets.properties -l b.jtl \
  -Jjmeter.save.saveservice.output_format=xml \
  -Jjmeter.save.saveservice.response_data=true
grep -n placeholder b.jtl
0
13:adminPassword=placeholder-admin-password
14:apiKey=placeholder-api-key

The CSV file contains neither value; the XML file with response data contains both, because the Debug Sampler put them in its response. The rules that follow from this:

  • Save responses only on error. Keep the main -l file in CSV without bodies, and capture failed responses with a separate errors-only listener, as described in Listeners & Results Files. Leave Save Request Headers and Save Sampler Data off: they contain the Authorization: Bearer header and the login body.

  • Remove debugging elements before a load run. Disable or delete Debug Samplers, Debug PostProcessors and View Results Tree listeners; the manual says View Results Tree must not be used during load tests anyway.

  • Keep Functional Test Mode off in load-test plans.

  • Do not put secrets or personal fields in sample_variables. Every listed variable becomes a column of every results row.

  • Use synthetic data. users.csv holds test accounts created for the test, and search terms are made up. If a test needs production-like data, use a masked or generated copy, never a raw production extract, and let the data owner decide whether it may leave the production environment.

  • Treat results as data with an owner. Store JTL files, dashboards and logs where the access rules match their content, set a retention period for CI artefacts, and delete error bodies once the investigation is over. Personal data in a results file falls under the same data-protection rules as personal data anywhere else.

The recorder root CA

To record HTTPS, the HTTP(S) Test Script Recorder acts as a deliberate man in the middle: it generates a root CA, exports it as ApacheJMeterTemporaryRootCA.crt, and signs a certificate for every host the browser visits. Once a browser trusts that root CA, it trusts every certificate signed by it, with no warning, until the certificate expires or is removed. The component reference states the consequence plainly: anyone who gets hold of the keystore and its password can create certificates that browsers trusting the JMeter root CA will accept. For that reason JMeter generates random keystore and key passwords (kept in the local preferences area) and uses a short validity, 7 days by default (proxy.cert.validity), and the manual asks that only trusted users have access to the host with the keystore. The certificate’s own subject repeats the warning: _ JMeter Root CA for recording (INSTALL ONLY IF IT S YOURS).

How to keep the exposure small:

  • Trust the CA in a throw-away browser profile, not in the operating system store and not in your everyday browser profile. Delete the profile, or remove the certificate, when recording is over.

  • Never install it on a shared machine, and never install a JMeter CA that someone else generated.

  • Keep the default 7-day validity. A longer proxy.cert.validity trades convenience for a longer window of exposure if the keystore leaks.

  • Protect proxyserver.jks. It lives in the JMeter bin directory by default (proxy.cert.directory). Do not commit it, copy it between machines or bake it into images; delete it to force a fresh CA, as shown on the recording page.

  • Stop the recorder when you are done. It listens on port 8888 by default; while it runs, anything that can reach that port can send traffic through it. Keep the port closed to other machines with the host firewall.

  • Record only test accounts, as explained above: recorded bodies end up in the plan.

The step-by-step installation, regeneration and the certificate properties are on Recording & cURL Import. Tools → Import from cURL needs no certificate at all and is the safer way to capture a few API calls.

RMI over SSL for distributed runs

In a distributed test, the controller sends the whole test plan to each remote server over Java RMI, and each server executes it. The remote testing chapter puts the risk directly: JMeter is basically a remote execution agent on both the server and the client side, which a malicious party could use to gain further access once it has compromised one of them. Since JMeter 4.0, RMI uses SSL by default; the keystore must exist on every node, or SSL must be switched off with server.rmi.ssl.disable=true.

Never disable SSL on a network you do not fully control. Turning it off makes the remote servers accept test plans, and therefore code, from anyone who can reach their RMI ports. The books (Pro Apache JMeter ch. 6, JMeter Cookbook ch. 5, JMeter 2.11 to 3.0) describe distributed testing before SSL existed; follow the current manual instead. The page on distributed testing later in this section covers the topology, ports and sample senders; this section covers only the security side.

The RMI SSL properties

Property Default Meaning

server.rmi.ssl.keystore.type

JKS

Keystore type; JKS and PKCS12 are commonly supported.

server.rmi.ssl.keystore.file

rmi_keystore.jks

Keystore with the private key.

server.rmi.ssl.keystore.password

changeit

Keystore password.

server.rmi.ssl.keystore.alias

rmi

Alias of the key.

server.rmi.ssl.truststore.type

value of the keystore type

Truststore type.

server.rmi.ssl.truststore.file

value of the keystore file

File with the trusted certificate.

server.rmi.ssl.truststore.password

value of the keystore password

Truststore password.

server.rmi.ssl.disable

false

true turns SSL off for RMI.

Creating and distributing the keystore

The manual’s simplest setup is one key pair shared by all servers and the controller, created with bin/create-rmi-keystore.sh (or .bat) and copied to every node. In 5.6.3 that script runs keytool with a fixed store password changeit, the alias rmi, a 2048-bit RSA key and a validity of 7 days, so the keystore has to be recreated before each test campaign. The fixed password is the documented default, which is acceptable only because the file itself must stay private; anyone with the file can join the cluster.

A keystore with its own password works the same way when the properties point at it. The following was run with the JDK 21 keytool; the password is a placeholder:

keytool -genkeypair -keyalg RSA -keysize 3072 -alias rmi -validity 30 \
  -storetype PKCS12 -keystore rmi_keystore.p12 \
  -storepass 'placeholder-change-me' -dname "CN=rmi, OU=Load testing, O=Bookshelf"
Generating 3072-bit RSA key pair and self-signed certificate (SHA384withRSA) with a validity of 30 days
	for: CN=rmi, OU=Load testing, O=Bookshelf
# rmi-ssl.properties - same file on the controller and every server, chmod 600
server.rmi.ssl.keystore.type=PKCS12
server.rmi.ssl.keystore.file=/opt/jmeter/secrets/rmi_keystore.p12
server.rmi.ssl.keystore.password=placeholder-change-me
server.rmi.ssl.keystore.alias=rmi

Start each server with the file (jmeter-server passes extra arguments to JMeter):

jmeter-server -Djava.rmi.server.hostname=10.0.0.21 -q rmi-ssl.properties

On a 5.6.3 server the first line of output names the SSL socket factories and the keystore in use (shown here with the address and path of the example, and abbreviated):

Created remote object: UnicastServerRef2 [liveRef: [endpoint:[10.0.0.21:61528,SSLRMIServerSocketFactory(host=/10.0.0.21, keyStoreLocation=/opt/jmeter/secrets/rmi_keystore.p12, type=PKCS12, trustStoreLocation=/opt/jmeter/secrets/rmi_keystore.p12, type=PKCS12, alias=rmi),SSLRMIClientSocketFactory(...)](local),objID:[...]]]

A server started without a keystore refuses to start rather than falling back to plain RMI:

Server failed to start: java.rmi.server.ExportException: Listen failed on port: 0; nested exception is:
	java.io.FileNotFoundException: rmi_keystore.jks (No such file or directory)

That message means "create or copy the keystore", not "disable SSL". A controller and a server holding different keystores fail the TLS handshake with PKIX path building failed, which is the same protection working as intended.

Network and host hardening

  • Fix and firewall the ports. The registry listens on 1099 (server_port); set server.rmi.localport on the servers and client.rmi.localport on the controller (which opens up to three ports from that base) so that the firewall can allow exactly those ports, and only between the controller and the servers. With SSL enabled, a fixed client.rmi.localport broke every remote start in a 5.6.3 test run; Distributed Testing shows the error and the alternative (leave it at 0 and allow the workers to reach the controller’s subnet).

  • Use a private network for the controller-to-server traffic, separate from the network the load goes to.

  • Run the servers only for the test. -X on the controller, or server.exitaftertest=true on a server, makes the server exit after the test instead of waiting for the next plan.

  • Run JMeter as an unprivileged user, with write access only to its own working directories.

  • The security manager. The manual describes enabling the Java security manager on distributed nodes (-Djava.security.manager and a java.policy file in setenv.sh) to restrict what a plan can do. That mechanism is deprecated for removal since Java 17 (JEP 411) and permanently disabled in Java 24 (JEP 486), where enabling it at start-up is an error. On the Java 21 this guide recommends it still works, with a deprecation warning, but it has no future; network isolation and least-privilege accounts are the lasting controls.

The BeanShell server

The BeanShell server is a different thing from the BeanShell elements (sampler, pre-processor, assertion). When enabled, the BeanShell interpreter inside JMeter listens on two ports: an HTTP port and, on the next port, a telnet port. The best-practices chapter is unambiguous: there is no security; anyone who can connect to the port can issue any BeanShell command, which gives unrestricted access to the JMeter application and the host. Do not enable it unless the ports are protected against access, for example by a firewall.

Property Default Meaning

beanshell.server.port

0 (server not started)

Non-zero starts the HTTP server on that port and the telnet server on the next one (for example 9000 and 9001).

beanshell.server.file

../extras/startup.bsh

Initialisation file processed by the server.

It is off by default; the rule is to keep it that way. A quick check of the properties files a generator uses:

grep -nE '^[[:space:]]*beanshell\.server\.port[[:space:]]*=' \
  "$JMETER_HOME"/bin/jmeter.properties "$JMETER_HOME"/bin/user.properties *.properties

On a default 5.6.3 installation this prints nothing: the line in jmeter.properties is commented out (#beanshell.server.port=9000). Any uncommented, non-zero value is a finding. If live inspection of a running test is needed, use the CLI shutdown port commands (HeapDump, ThreadDump) or the Backend Listener described on Real-time Monitoring with Backend Listener instead.

The books still use BeanShell pre- and post-processors (Pro Apache JMeter ch. 3); the current manual recommends JSR223 elements with Groovy, see Scripting with JSR223 & Groovy.

Trusting .jmx files

The JMeter security model is stated on the project’s security page: JMeter executes the workload in the .jmx file, which may include arbitrary code, so the model assumes you trust the .jmx input; even opening a .jmx file may in some cases trigger code execution. Evaluating untrusted plans requires isolation that you provide.

In practice:

  • Treat a .jmx file like a script. Review changes to plans in pull requests the way code is reviewed, paying attention to JSR223 and BeanShell elements, OS Process Samplers and functions such as __groovy.

  • Do not open a plan from an unknown source in the GUI on your workstation. Read the XML first, or open it in a disposable virtual machine or container with no credentials and no network access to anything that matters.

  • Load generators fetch plans from a trusted location only (the repository, a signed artefact), never from a shared upload folder.

Keeping JMeter, Java and plugins updated

  • JMeter. The best-practices chapter asks users to use the latest version, to read the changes list of each release, and to "absolutely avoid" versions older than three versions before the last one. Verify each download with its SHA-512 checksum and PGP signature, as shown in Installation & Configuration.

  • Java. The getting-started chapter advises installing the latest minor version of your Java major version for security and performance reasons.

  • Security announcements. Vulnerabilities in JMeter are reported privately to security@apache.org, fixed in a new release, and then announced publicly. At the time of writing the JMeter security page describes the model and the reporting process but does not list individual CVEs; read the changes page of each new release, and report a suspected vulnerability to the private list rather than in a public issue.

  • Bundled libraries. JMeter ships many third-party jars in lib/. Replacing them by hand is unsupported; take the next JMeter release instead. For 6.0, changes.xml on master plans Java 17 as the minimum and upgrades several bundled libraries (for example Log4j 2.26.1 and Groovy 5.1.3), and fixes a CLI log message that wrongly reported keystore passwords as missing (PR 6372). These are planned, not released.

  • Plugins. Plugins are updated separately from JMeter, through the Plugins Manager or your build. Pin their versions and review the update notes like any other dependency.

Judging third-party plugins

A plugin is a jar in lib/ext that runs inside the JMeter JVM with the same permissions as JMeter itself: it can read the plan, the variables (secrets included), the file system and the network. jmeter-plugins.org is an independent community catalogue, not an Apache project, and the Apache security process does not cover it. The checklist on Plugins & Plugins Manager (activity, maintainer and archive status, compatibility, licence, dependencies, download source, SNAPSHOT versions, a core alternative) applies; the security reading of it is:

  • Prefer core. Every plugin you avoid is one less third-party code base on every generator.

  • Check the maintainer and the history. An archived project, or a catalogue id that changed hands (as happened to the gRPC Request plugin), deserves a second look at who now publishes the jar.

  • Check dependencies for known vulnerabilities. Old copies of common libraries inside a plugin are the usual problem; release notes that list CVE fixes show that the maintainer tracks them.

  • Pin versions and record checksums, so a rebuilt load generator gets exactly the jars that were reviewed.

  • Control what the Plugins Manager sends and fetches. Set jpgc.repo.sendstats=false to stop the usage statistics, and point jpgc.repo.address at an internal mirror when generators must not reach the internet.

  • Install the same reviewed set on every node of a distributed test.

A pre-flight checklist

Done Check

☐

Written approval names the target, window, peak load, stop conditions and contacts.

☐

Hosting, CDN and third-party providers informed or replaced by stubs; their testing policies read.

☐

Smoke run passed; load increases in steps.

☐

No passwords, tokens or keys in the .jmx (grep is clean); secrets come from -q files or CI secrets.

☐

Test accounts and synthetic data only.

☐

Main results file in CSV without bodies or headers; errors-only listener for failed responses.

☐

No Debug Sampler, View Results Tree or Functional Test Mode in the load run.

☐

Recorder stopped; JMeter root CA removed from browsers; proxyserver.jks not copied anywhere.

☐

Distributed nodes use RMI over SSL with a current keystore; RMI ports fixed and firewalled.

☐

beanshell.server.port not set.

☐

JMeter, Java and plugins at reviewed, pinned, current versions.

☐

Results stored with access control and a retention period.

From the books to 5.6 to 6.0

Era Security-relevant state

JMeter 2.11 to 3.0 (the books)

Distributed testing over plain RMI (Pro Apache JMeter ch. 6, JMeter Cookbook ch. 5). Recording through the proxy and the WorkBench (Pro Apache JMeter ch. 4). BeanShell elements common in examples (Pro Apache JMeter ch. 3).

JMeter 4.0 to 5.5

RMI over SSL becomes the default (4.0), with create-rmi-keystore.sh and server.rmi.ssl.disable. WorkBench removed (4.0); recording uses the HTTP(S) Test Script Recorder with a generated root CA, random passwords and a 7-day validity. JSR223 with Groovy replaces BeanShell as the recommended scripting.

JMeter 5.6 to 5.6.3

Same model. The security page states the trust-the-.jmx model and the private reporting process.

JMeter 6.0 (planned)

Per changes.xml on master: Java 17 or later required, updated bundled libraries, the keystore-password logging fix. Nothing planned changes the RMI SSL, recorder CA or BeanShell server behaviour described here. Re-check when 6.0 is released.

Pitfalls

  • "It’s only staging." Staging often shares a database cluster, network link, identity provider or third-party sandbox with production. Agree the test as if it were production.

  • Forgetting the generator’s side. Load generators in the cloud are bound by their provider’s testing policy too.

  • Real credentials in recordings. The recorder drops the Authorization header but not the login body.

  • -J for secrets. Convenient, and visible in ps and shell history.

  • A Debug Sampler left in the plan. It copies every variable, secrets included, into a response that a listener may save.

  • Uploading the whole results folder as a CI artefact. Error bodies and logs go with it; upload the dashboard and the CSV, and keep error bodies in a restricted location.

  • server.rmi.ssl.disable=true copied from an old tutorial. Create the keystore instead.

  • An expired RMI keystore. The default script creates keys valid for 7 days; regenerate before each campaign rather than switching SSL off.

  • A trusted JMeter CA left in the everyday browser for months, or a proxyserver.jks copied into a Docker image.

  • Opening a .jmx from an unknown source in the GUI. The security model assumes plans are trusted.

References