# How to Integrate testssl.sh with Nagios and Icinga for Automated TLS Monitoring

> Integrate testssl.sh with Nagios and Icinga for automated TLS monitoring. Leverage plugins to translate exit codes and output into service states for proactive SSL health and vulnerability alerts.

- Repository: [Dirk Wetter/testssl.sh](https://github.com/drwetter/testssl.sh)
- Tags: how-to-guide
- Published: 2026-03-01

---

**testssl.sh integrates with Nagios and Icinga through dedicated plugins that translate its exit codes and JSON/CSV output into monitoring service states, enabling automated TLS health checks and vulnerability alerting.**

The [`drwetter/testssl.sh`](https://github.com/drwetter/testssl.sh/blob/main/drwetter/testssl.sh) repository provides a pure Bash TLS scanner that supports seamless **testssl.sh Nagios Icinga integration** through wrapper plugins and standardized exit codes. By leveraging the tool's machine-readable output formats and documented `ERR_*` constants, security teams can automate SSL/TLS certificate and configuration monitoring within existing infrastructure without modifying the core scanner.

## Understanding the Integration Architecture

### Exit Codes and Machine-Readable Output

The core [`testssl.sh`](https://github.com/drwetter/testssl.sh/blob/main/testssl.sh) script defines specific exit codes (documented in [`doc/testssl.1.md`](https://github.com/drwetter/testssl.sh/blob/main/doc/testssl.1.md) and implemented in [`testssl.sh`](https://github.com/drwetter/testssl.sh/blob/main/testssl.sh) via `ERR_*` constants) that indicate scan status: 0 for success/no findings, 1 for minor issues, 2 for severe vulnerabilities, and higher values for fatal errors. Monitoring plugins map these numeric exit codes to service states (OK, WARNING, CRITICAL, UNKNOWN).

### Output Formats for Parsing

The scanner supports `--jsonfile` and `--csvfile` flags (defined in [`testssl.sh`](https://github.com/drwetter/testssl.sh/blob/main/testssl.sh) command-line parsing) to generate structured data. Plugins parse these files to extract specific vulnerability IDs, certificate expiration dates, and cipher suite ratings, providing granular alert context beyond simple pass/fail status.

## Available Monitoring Plugins

### Nagios Python Plugin

The official Nagios integration is available via `dnmvisser/nagios-testssl`, a Python 3 plugin that wraps [`testssl.sh`](https://github.com/drwetter/testssl.sh/blob/main/testssl.sh) execution. It translates JSON output and exit codes into Nagios-compliant status messages and performance data.

### Icinga 2 Bash Wrapper

For Icinga 2 environments, the [`check_testssl.sh`](https://github.com/drwetter/testssl.sh/blob/main/check_testssl.sh) Bash script (maintained at `gitgud.malvager.net/Wazakindjes/icinga2_plugins`) provides native integration. It handles argument passing, temporary file management for JSON output, and exit code mapping to Icinga service states.

## Implementation Guide

### Configuring the Nagios Plugin

Install the Python plugin in your Nagios `libexec` directory, then define the command and service objects in your configuration files.

Example [`commands.cfg`](https://github.com/drwetter/testssl.sh/blob/main/commands.cfg) definition:

```bash
define command{
    command_name    check_testssl
    command_line    /usr/local/nagios/libexec/check_testssl.py -H $HOSTADDRESS$ -p $ARG1$
}

```

Example service definition:

```bash
define service{
    use                     generic-service
    host_name               web01.example.com
    service_description     TLS Scan
    check_command           check_testssl!443
}

```

### Setting Up the Icinga 2 Command

Define the CheckCommand object in Icinga 2's [`commands.conf`](https://github.com/drwetter/testssl.sh/blob/main/commands.conf) and apply the service in [`services.conf`](https://github.com/drwetter/testssl.sh/blob/main/services.conf).

Example [`commands.conf`](https://github.com/drwetter/testssl.sh/blob/main/commands.conf):

```bash
object CheckCommand "check_testssl" {
  command = [ "/usr/lib/icinga2/plugins/check_testssl.sh" ]
  arguments = {
    "-H" = "$address$"
    "-p" = "$testssl_port$"
    "--jsonfile" = "/var/tmp/testssl-${host.name}-${testssl_port}.json"
  }
}

```

Example [`services.conf`](https://github.com/drwetter/testssl.sh/blob/main/services.conf):

```bash
object Service "testssl" {
  host_name = "mail.example.com"
  check_command = "check_testssl"
  vars.testssl_port = "25"
}

```

### Direct Integration Without Wrappers

For custom monitoring solutions or lightweight setups, invoke [`testssl.sh`](https://github.com/drwetter/testssl.sh/blob/main/testssl.sh) directly and map its exit codes.

Example Bash wrapper script:

```bash
#!/usr/bin/env bash
HOST="example.com"
PORT=443

# Run testssl.sh and capture exit status

/testssl.sh --quiet -p -s -P -f -S -h -U -q --ip=one --color 0 "$HOST:$PORT"
RET=$?

# Map exit codes to monitoring states

case $RET in
    0)  STATE=0; MSG="OK – TLS scan passed" ;;
    1)  STATE=1; MSG="WARNING – Minor issues detected" ;;
    2)  STATE=2; MSG="CRITICAL – Severe vulnerabilities found" ;;
    *)  STATE=3; MSG="UNKNOWN – Unexpected exit code $RET" ;;
esac

echo "$MSG"
exit $STATE

```

## Key Files and Exit Code Reference

Understanding the core files helps troubleshoot integration issues:

| File | Role | Location |
|------|------|----------|
| [`testssl.sh`](https://github.com/drwetter/testssl.sh/blob/main/testssl.sh) | Core scanner containing `ERR_*` exit code constants and output formatting logic | Repository root |
| [`doc/testssl.1.md`](https://github.com/drwetter/testssl.sh/blob/main/doc/testssl.1.md) | Man page documenting `--jsonfile`, `--csvfile`, and exit code semantics | `doc/` directory |
| [`Readme.md`](https://github.com/drwetter/testssl.sh/blob/main/Readme.md) | Lists official third-party plugins and integration hints | Repository root |
| `utils/` | Helper scripts for OpenSSL compilation (useful if monitoring hosts need specific OpenSSL versions) | `utils/` directory |

Exit code mapping for monitoring integration:

- **0**: OK – No findings or informational results only
- **1**: WARNING – Minor issues detected (e.g., deprecated protocols)
- **2**: CRITICAL – Severe vulnerabilities found (e.g., Heartbleed, CRIME)
- **3+**: UNKNOWN – Scan errors or fatal conditions

## Summary

- **testssl.sh Nagios Icinga integration** relies on wrapper plugins that translate the scanner's exit codes and structured output into monitoring service states.
- The **Nagios Python plugin** (`dnmvisser/nagios-testssl`) and **Icinga 2 Bash wrapper** ([`check_testssl.sh`](https://github.com/drwetter/testssl.sh/blob/main/check_testssl.sh)) are the officially referenced integrations listed in the repository's [`Readme.md`](https://github.com/drwetter/testssl.sh/blob/main/Readme.md).
- **Exit code mapping** (0=OK, 1=WARNING, 2=CRITICAL) is defined in [`testssl.sh`](https://github.com/drwetter/testssl.sh/blob/main/testssl.sh) via `ERR_*` constants and documented in [`doc/testssl.1.md`](https://github.com/drwetter/testssl.sh/blob/main/doc/testssl.1.md).
- **Machine-readable output** via `--jsonfile` and `--csvfile` enables granular alerting on specific vulnerabilities, certificate expiration, and cipher suite ratings.
- Direct integration is possible by invoking [`testssl.sh`](https://github.com/drwetter/testssl.sh/blob/main/testssl.sh) from custom scripts and mapping the numeric exit codes to monitoring system states.

## Frequently Asked Questions

### Does testssl.sh require modification to work with Nagios or Icinga?

No modification of the core [`testssl.sh`](https://github.com/drwetter/testssl.sh/blob/main/testssl.sh) script is required. The integration works through external wrapper plugins that execute the scanner as a subprocess and translate its standardized exit codes and JSON/CSV output into the monitoring system's native service states. The repository's [`Readme.md`](https://github.com/drwetter/testssl.sh/blob/main/Readme.md) explicitly lists these third-party plugins as the recommended integration method.

### What exit codes does testssl.sh return for monitoring systems?

The scanner returns numeric exit codes defined by `ERR_*` constants in [`testssl.sh`](https://github.com/drwetter/testssl.sh/blob/main/testssl.sh): 0 indicates success or informational findings only, 1 indicates minor issues (WARNING state), 2 indicates severe vulnerabilities (CRITICAL state), and values 3 or higher indicate scan errors or fatal conditions (UNKNOWN state). These codes are documented in [`doc/testssl.1.md`](https://github.com/drwetter/testssl.sh/blob/main/doc/testssl.1.md) and map directly to Nagios and Icinga service state conventions.

### Can I monitor specific TLS vulnerabilities rather than just pass/fail status?

Yes, by using the `--jsonfile` or `--csvfile` output options documented in [`testssl.sh`](https://github.com/drwetter/testssl.sh/blob/main/testssl.sh) and [`doc/testssl.1.md`](https://github.com/drwetter/testssl.sh/blob/main/doc/testssl.1.md). Monitoring plugins parse these structured files to extract specific vulnerability IDs (such as Heartbleed, CRIME, or BEAST), certificate expiration dates, and cipher suite ratings. This allows for granular service checks that can trigger different alert thresholds based on the severity of specific findings rather than relying solely on the aggregate exit code.

### Is there a performance overhead when running testssl.sh from monitoring systems?

The overhead is minimal because [`testssl.sh`](https://github.com/drwetter/testssl.sh/blob/main/testssl.sh) is a pure Bash script that executes efficiently against target hosts. However, full TLS scans can take 30-60 seconds depending on network latency and the number of checks enabled (protocols, cipher suites, vulnerabilities). For monitoring purposes, you can optimize performance by restricting scans to specific checks using flags like `-p` (protocols), `-s` (cipher suites), or `-H` (HEARTBLEED only) as documented in [`doc/testssl.1.md`](https://github.com/drwetter/testssl.sh/blob/main/doc/testssl.1.md), reducing execution time to 5-10 seconds per host.