Performance Tuning testssl.sh for Large-Scale Scans: A Technical Guide
Optimize testssl.sh for large-scale scanning by enabling parallel mass-testing mode, tuning the MAX_PARALLEL environment variable to match your CPU cores, using --ssl-native to reduce system calls, and selectively disabling expensive checks like Heartbleed with --disable-* flags.
testssl.sh is a pure-bash TLS/SSL scanner capable of assessing thousands of hosts efficiently without heavy runtime dependencies. For security teams running enterprise-wide audits, understanding how to leverage its built-in parallelization engine and timeout controls is essential for throughput. This guide explains the specific shell variables and command-line flags implemented in the drwetter/testssl.sh codebase that govern performance during mass assessments.
Core Performance Architecture
testssl.sh deliberately separates data-gathering (socket/openssl calls) from result processing, enabling high concurrency without memory bloat. The primary performance controls are implemented as environment variables and flags in the main driver script.
Mass Testing Mode and Parallel Execution
The scanner operates in two distinct modes controlled by the MASS_TESTING_MODE variable, defined at line 178 in testssl.sh as MASS_TESTING_MODE=${MASS_TESTING_MODE:-serial}【4†L178-L180】.
- Serial mode: Processes targets one by one (default)
- Parallel mode: Spawns multiple child processes up to the limit specified by
MAX_PARALLEL
When running parallel mass-testing via --parallel or --file, the script stores command lines in the PARALLEL_TESTING_CMDLINE array and manages child PIDs in PARALLEL_TESTING_PID. The parent process sleeps PARALLEL_SLEEP seconds (default 1s) between launches to avoid scheduler overwhelm, then collects results via get_next_message_testing_parallel_result.
Timeout and Retry Variables
Several environment variables control how long the scanner waits for network responses before aborting. These are defined in testssl.sh and documented in doc/testssl.1.md:
MAX_PARALLEL: Default 20, controls concurrent child processes【5†L444-L453】MAX_WAIT_TEST: Default 1200 seconds (20 minutes) per test before abortion【5†L447-L449】MAX_SOCKET_FAIL: Tolerance for failed socket connect attemptsMAX_OSSL_FAIL: Tolerance for OpenSSL command failures【4†L11-L14】
Optimizing the Parallel Engine
Tuning Concurrency Limits
For large-scale deployments, export MAX_PARALLEL before execution to match your hardware capacity. On a 16-core host, setting this to 64 allows full CPU utilization while the bash-based architecture keeps memory usage minimal (only integer counters for NR_PARALLEL_TESTS and NEXT_PARALLEL_TEST_TO_FINISH).
# Export higher limit for beefy hardware
export MAX_PARALLEL=64
# Run parallel mass-testing with tuned timeouts
testssl.sh --parallel --file targets.txt \
--max-wait-test 900 \
--max-socket-fail 5 \
--max-ossl-fail 5 \
--warnings batch
The targets.txt file should contain one complete command line per line (e.g., testssl.sh -p example.com:443), allowing different scan profiles per target.
Reducing Overhead with Native SSL
The --ssl-native flag replaces the mixed socket+openssl approach with a mostly OpenSSL-only implementation, significantly cutting system calls. According to the manual in doc/testssl.1.md【4†L136-L138】, this provides better performance than the default hybrid method while maintaining accuracy.
Avoid --fast: This deprecated flag (marked at line 24903 in testssl.sh【6†L24903-L24904】) skips validation steps and can produce unreliable results. Modern scans should use --ssl-native combined with selective test disabling instead.
Selective Test Disabling
Use --disable-* or --no-* flags to skip expensive client-simulation checks. Parsing for these flags occurs around line 24510 in testssl.sh【6†L24510-L24512】. The most time-consuming checks to consider disabling include:
--disable-heartbleed--disable-ccs-injection--disable-ticketbleed
testssl.sh example.com \
--ssl-native \
--disable-heartbleed \
--disable-ccs-injection \
--max-parallel 30 \
--max-wait-test 600
Container and Binary Optimization
High-Performance OpenSSL
Point to a modern OpenSSL binary using the OPENSSL environment variable. Newer versions (3.2+) include TLS 1.3 optimizations and larger cipher lists that reduce execution time for --each-cipher checks.
export OPENSSL=/opt/openssl-3.2/bin/openssl
testssl.sh -p mail.example.org
Docker Image Selection
The project provides multiple Dockerfiles with different libc implementations. As documented in Dockerfile.md【4†L46-L49】, the OpenSUSE-based image uses glibc and performs a few percent faster than the Alpine variant (musl). For maximum throughput in containerized deployments, prefer the standard Dockerfile over Dockerfile.alpine.
Profiling Scan Performance
Enable DEBUGTIME to generate per-process timestamp files (/tmp/testssl-<PID>.time) for bottleneck identification:
export DEBUGTIME=true
testssl.sh --parallel --file biglist.txt
paste /tmp/testssl-*.{time,log} > profiling.tsv
For interrupted mass-testing runs, the utils/resume.sh helper allows you to resume from failure points without re-scanning completed targets.
Summary
- Enable parallel mode: Use
--parallelor setMASS_TESTING_MODE=parallelto process multiple targets simultaneously rather than serially. - Match concurrency to hardware: Export
MAX_PARALLELvalues between 16-64 depending on available CPU cores and network bandwidth. - Replace deprecated flags: Use
--ssl-nativeinstead of--fastfor safe performance gains. - Skip expensive tests: Apply
--disable-heartbleed,--disable-ccs-injection, and similar flags to avoid time-consuming client simulations. - Optimize timeouts: Lower
MAX_WAIT_TESTfrom the default 1200s for responsive networks, or raiseMAX_SOCKET_FAILfor unstable environments. - Use modern OpenSSL: Set the
OPENSSLenvironment variable to a locally compiled binary for faster cryptographic operations. - Profile with DEBUGTIME: Generate timing logs to identify which specific tests consume the most wall-clock time.
Frequently Asked Questions
How many parallel processes should I run for large-scale scanning?
Set MAX_PARALLEL to approximately 2-4 times your CPU core count, depending on network latency. The default of 20 suits most 8-core systems, but 16-core servers handling internal networks can handle 64 parallel processes. Monitor system load; since testssl.sh uses pure bash with minimal memory overhead per child, CPU and file descriptor limits are typically the binding constraints.
Is the --fast flag recommended for speeding up scans?
No. The --fast flag is deprecated as of the 3.3dev branch, with the source code explicitly warning that it "can have some undesired side effects thus it is not recommended to use anymore"【6†L24903-L24904】. Instead, use --ssl-native to reduce system call overhead while maintaining check integrity, combined with --disable-* flags to skip specific expensive vulnerability tests.
Why do my parallel scans timeout on slow networks?
The MAX_WAIT_TEST variable defaults to 1200 seconds (20 minutes) per individual test. If scanning high-latency networks or hosts with aggressive rate-limiting, either increase this value or raise MAX_SOCKET_FAIL and MAX_OSSL_FAIL to tolerate transient connection failures without aborting the entire scan. Alternatively, use --max-wait-test to set shorter timeouts for responsive internal networks.
Which Docker image performs better for bulk scanning?
The glibc-based OpenSUSE image (built from the standard Dockerfile) performs slightly faster than the Alpine/musl variant (Dockerfile.alpine). According to Dockerfile.md【4†L46-L49】, the musl libc implementation in Alpine adds a small overhead to cryptographic operations. For maximum throughput in production environments, use the OpenSUSE-based container or run natively with a custom-compiled OpenSSL binary.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →