Skip to main content

A Nagios plugin for monitoring MySQL servers, directly or through an SSH tunnel

Project description

๐Ÿฌ Check MySQL

PyPI version Python versions License: MIT Build Publish Quality Gate Status Maintainability Rating Reliability Rating Security Rating Bugs Vulnerabilities Code Smells Technical Debt

A comprehensive Nagios plugin for monitoring MySQL and MariaDB servers, either directly (PyMySQL) or through an SSH tunnel (sshtunnel/paramiko). Built with modern Python practices and designed for enterprise monitoring environments.

โœจ Features

  • ๐Ÿ”Œ Dual Connectivity - Direct TCP connection or SSH tunnel through a bastion host
  • ๐ŸŽฏ 22 Checks - Connectivity ping (client/server versions), uptime, connection usage, replication health, slow queries, query latency, arbitrary SQL, plus the cache-hitrate and load checks backported from check_mysql_health
  • ๐Ÿ” Security Audit - CIS-aligned mysql.user audit: anonymous accounts, missing/weak/expired passwords, wildcard hosts, remote root, dangerous privileges
  • ๐Ÿง™ Guided Setup - check_mysql init writes the config, creates the monitoring user and tests the connection
  • ๐Ÿ“Š Nagios Compatible - Standard exit codes, performance data and range-based thresholds
  • ๐Ÿ—๏ธ Clean Architecture - Modular design with testable components
  • ๐Ÿ”ง Flexible Configuration - File-based configuration with CLI overrides (-H, -P)
  • ๐Ÿ“ˆ Verbose Logging - Multi-level debugging support (-v, -vv, -vvv)
  • ๐Ÿ Modern Python - Python 3.10+ with strict typing throughout

๐Ÿš€ Quick Start

Installation

# Create virtual environment (recommended)
python -m venv /usr/local/libexec/nagios/check_mysql
source /usr/local/libexec/nagios/check_mysql/bin/activate

# Install from PyPI
pip install check-mysql-nagios

# Or install from source
pip install git+https://github.com/lduchosal/check_mysql.git

Basic Usage

# Guided setup: prompts for the connection (SSH tunnel supported), writes
# check_mysql.ini, shows the CREATE USER SQL, optionally creates the
# monitoring user on the server and tests the connection.
check_mysql init

# Non-interactive: just write the default template
check_mysql init --yes

# Check connectivity and report the client and server versions
check_mysql ping

# Check server uptime (alert on recent restart)
check_mysql uptime

# Check connection usage (percent of max_connections)
check_mysql connections -W 80 -C 95

# Check replication lag and thread state
check_mysql replication -W 60 -C 300

# Check the slow queries counter
check_mysql slowqueries -W 100 -C 1000

# Check SELECT 1 round-trip latency
check_mysql latency -W 100 -C 500

# Point at another server without editing the config
check_mysql uptime -H db2.example.com -P 3307

๐Ÿ“‹ Available Commands

Command Description Value Default Thresholds
init Guided setup: config, monitoring user (SQL shown or created for you), connection test โ€” --yes for non-interactive, --force to overwrite - -
ping Connectivity check reporting the client (PyMySQL) and server versions; CRITICAL when the server is unreachable milliseconds -
uptime Seconds since server start seconds W:3600:, C:300:
connections Threads_connected vs max_connections percent W:80, C:95
replication Replication lag, thread state seconds behind W:60, C:300
slowqueries Slow_queries counter since start count W:100, C:1000
latency SELECT 1 round-trip time milliseconds W:100, C:500
threadcache Thread cache hitrate (Threads_created vs Connections) percent W:90:, C:80:
querycache Query cache hitrate โ€” MariaDB only, UNKNOWN on MySQL 8+ percent W:90:, C:80:
querycacheprunes Qcache_lowmem_prunes per second โ€” MariaDB only rate/s W:1, C:10
keycache MyISAM key cache hitrate percent W:99:, C:95:
tablecache Table cache hitrate (Open_tables vs Opened_tables) percent W:99:, C:95:
bufferpool InnoDB buffer pool hitrate percent W:99:, C:95:
bufferpoolwaits Innodb_buffer_pool_wait_free per second rate/s W:1, C:10
logwaits Innodb_log_waits per second rate/s W:1, C:10
tablelocks Table lock contention (waited vs immediate) percent W:1, C:2
indexusage Index usage vs full scans (Handler_read_*) percent W:90:, C:80:
tmpdisktables Share of temporary tables created on disk percent W:25, C:50
openfiles Open_files vs open_files_limit percent W:80, C:95
longrunning Queries running longer than 60s (needs PROCESS) count W:10, C:20
security Over-privileged or insecure accounts: anonymous, passwordless, weak or expired password, wildcard % host, remote root, dangerous privileges reachable remotely (needs SELECT on mysql.user) count W:0, C:5
sql Scalar result of an arbitrary statement (--sql "SELECT ...") scalar -

Thresholds are standard Nagios ranges: 95 alerts above 95, 300: alerts below 300, 10:20 alerts outside the interval.

check_mysql_health Heritage

The threadcache โ€ฆ longrunning block and the sql command are backported from lausser's check_mysql_health with the same default thresholds (security is check_mysql's own). One deliberate difference: the original computes rates as deltas between two runs persisted in a state file; check_mysql reports averages since server start instead, which needs no local state. The query cache checks (querycache, querycacheprunes) apply to MariaDB only โ€” the query cache was removed in MySQL 8.0 and those commands exit UNKNOWN there.

Replication Semantics

  • OK/WARNING/CRITICAL on Seconds_Behind_Source against the thresholds
  • CRITICAL immediately when the IO or SQL thread is stopped (the last replication error is included in the output)
  • CRITICAL immediately when the lag is NULL while threads run
  • UNKNOWN when the server is not a replica
  • Both modern (Replica_*/Source_*) and legacy (Slave_*/Master_*) column names are supported

Security Audit Semantics

The security command reads mysql.user (one SELECT, no other table) and flags accounts matching any of these criteria, aligned with the CIS Oracle MySQL Benchmark and mysql_secure_installation:

Check Condition on the mysql.user row Reported as Reference
Anonymous account User is empty anonymous account CIS 4.11
Missing password Password and authentication_string both empty, on a password-based plugin no password CIS 4.10
Weak password mysql_native_password hash matches a common password (offline wordlist, unsalted hashes only) weak password MySQLTuner
Expired password password_expired = Y (stale credential left in place) password expired CIS 4.x
Wildcard host Host is % or empty (reachable from any address) wildcard host CIS 4.9
Remote root User is root with a host other than localhost/127.0.0.1/::1 root reachable remotely mysql_secure_installation
Remote dangerous privileges Non-local host holding SUPER, GRANT OPTION, FILE, PROCESS, SHUTDOWN, CREATE USER, RELOAD, CREATE/ALTER ROUTINE, EVENT or TRIGGER โ€” or every *_priv column, reported as ALL PRIVILEGES remote privileges (โ€ฆ) CIS 5.2โ€“5.8

The value is the number of flagged accounts (an account with several findings counts once); the long output lists one 'user'@'host' line per account and the headline breaks the count down by category. Defaults: WARNING above 0, CRITICAL above 5. Run with -vvv to trace every criterion and its verdict per account (see Debug Mode).

What is deliberately not flagged:

  • Locked accounts (account_locked = Y) and the mysql_no_login plugin โ€” they refuse every connection, no attack surface. They are excluded from the audited count.
  • Socket/external authentication (unix_socket, auth_socket, PAM, LDAP, GSSAPI, Kerberos, Windows) โ€” an empty credential column is normal there, not a missing password.
  • Powerful local accounts โ€” root@localhost or debian-sys-maint@localhost are expected on every install; privileges are only flagged on remotely reachable accounts.
  • The monitoring user's % host โ€” the [mysql] account must be remotely reachable to do its job; it stays subject to every other check (no password, dangerous privileges).
  • Expected admins โ€” accounts listed in [security] admins are exempted from the remote-privileges check only (they are meant to be powerful) and stay subject to every other check.
  • Accounts listed in [security] allow โ€” exempted from every check (see the Configuration section below).
  • Scoped host patterns (10.0.%, %.example.com) โ€” only the pure % wildcard is flagged.

Known limits (candidates for later versions): salted-hash accounts cannot be tested for weak passwords offline (caching_sha2_password is salted by design โ€” only mysql_native_password is covered); legacy authentication plugins (mysql_native_password, pre-4.1 hashes โ€” CIS 4.7), absent validate_password policy, presence of the test database, non-admin grants on the mysql schema (CIS 5.1) and per-account TLS requirements are not yet checked.

โš™๏ธ Configuration

Run check_mysql init for a guided setup: it prompts for the connection settings (password generated by default, SSH tunnel supported), writes check_mysql.ini with mode 600, prints the CREATE USER SQL, and can create the monitoring user on the server (with admin credentials, through the tunnel if configured) then test the connection. Or create the file by hand โ€” it is searched in the working directory, then in /usr/local/etc/nagios and /etc/nagios; -c also accepts an absolute path:

Direct Connection

[mysql]
host = db.example.com
port = 3306
user = nagios
password = secret
# Optional default schema (useful with the sql command):
#database = mydb
# Connection timeout in seconds:
timeout = 10

Connection Through an SSH Tunnel

[mysql]
# Host/port as seen FROM THE BASTION
host = 10.0.0.12
port = 3306
user = nagios
password = secret
timeout = 10

[ssh]
host = bastion.example.com
port = 22
user = nagios
private_key = ~/.ssh/id_ed25519
# or: password = only-if-no-key

When the [ssh] section is present, the plugin opens the tunnel first and connects to MySQL through it; remove the section to connect directly.

MySQL Monitoring User

CREATE USER 'nagios'@'%' IDENTIFIED BY 'secret';
GRANT USAGE, REPLICATION CLIENT ON *.* TO 'nagios'@'%';
GRANT SELECT ON mysql.user TO 'nagios'@'%';

This is exactly what check_mysql init creates (or prints) for you.

REPLICATION CLIENT (or REPLICATION_SLAVE_ADMIN on MySQL 8+) is only needed for the replication command, and SELECT on mysql.user only for the security command. Add GRANT PROCESS ON *.* for the longrunning command (to see other users' queries), and the relevant SELECT grants for whatever the sql command queries.

The security audit is tuned through the optional [security] section (the full criteria and built-in exemptions are described under Security Audit Semantics above). Entries are user@host, exactly like the User and Host columns of mysql.user โ€” comma separated, % needs no escaping. allow exempts accounts from every check; admins lists accounts expected to be powerful, exempted from the remote-privileges check only:

[security]
allow = backup@10.0.0.5, debian-sys-maint@localhost, app@%
admins = dba@10.0.0.0/24, ops@%

๐Ÿ”ง Command Line Options

Option Description Example
-c, --config Configuration file path -c /custom/path/config.ini
-H, --hostname MySQL host (overrides [mysql]) -H db2.example.com
-P, --port MySQL port (overrides [mysql]) -P 3307
-W, --warning Warning threshold (Nagios range) -W 80
-C, --critical Critical threshold (Nagios range) -C 95
-v, --verbose Verbosity level -v, -vv, -vvv
--version Show version --version

Command-specific options: sql requires --sql "SELECT ..." (the statement whose first row / first column is checked); init accepts --yes (non-interactive, write the default template) and --force (overwrite an existing file).

๐Ÿข Nagios Integration

Command Definitions

# MySQL Commands
define command {
    command_name    check_mysql_ping
    command_line    $USER1$/check_mysql/bin/check_mysql ping -H $HOSTADDRESS$
}

define command {
    command_name    check_mysql_uptime
    command_line    $USER1$/check_mysql/bin/check_mysql uptime -H $HOSTADDRESS$
}

define command {
    command_name    check_mysql_connections
    command_line    $USER1$/check_mysql/bin/check_mysql connections -H $HOSTADDRESS$ -W 80 -C 95
}

define command {
    command_name    check_mysql_replication
    command_line    $USER1$/check_mysql/bin/check_mysql replication -H $HOSTADDRESS$ -W 60 -C 300
}

define command {
    command_name    check_mysql_slowqueries
    command_line    $USER1$/check_mysql/bin/check_mysql slowqueries -H $HOSTADDRESS$ -W 100 -C 1000
}

define command {
    command_name    check_mysql_latency
    command_line    $USER1$/check_mysql/bin/check_mysql latency -H $HOSTADDRESS$ -W 100 -C 500
}

define command {
    command_name    check_mysql_security
    command_line    $USER1$/check_mysql/bin/check_mysql security -H $HOSTADDRESS$
}

Every command in the table above follows the same pattern; all ship sensible default thresholds, so -W/-C can be omitted.

Service Definitions

# MySQL Services
define service {
    use                     generic-service
    service_description     MYSQL_PING
    check_command           check_mysql_ping
    hostgroup_name          mysql
}

define service {
    use                     generic-service
    service_description     MYSQL_UPTIME
    check_command           check_mysql_uptime
    hostgroup_name          mysql
}

define service {
    use                     generic-service
    service_description     MYSQL_CONNECTIONS
    check_command           check_mysql_connections
    hostgroup_name          mysql
}

define service {
    use                     generic-service
    service_description     MYSQL_REPLICATION
    check_command           check_mysql_replication
    hostgroup_name          mysql-replicas
}

define service {
    use                     generic-service
    service_description     MYSQL_SLOWQUERIES
    check_command           check_mysql_slowqueries
    hostgroup_name          mysql
}

define service {
    use                     generic-service
    service_description     MYSQL_LATENCY
    check_command           check_mysql_latency
    hostgroup_name          mysql
}

define service {
    use                     generic-service
    service_description     MYSQL_SECURITY
    check_command           check_mysql_security
    hostgroup_name          mysql
}

๐Ÿ—๏ธ Architecture

This plugin follows clean architecture principles with clear separation of concerns:

check_mysql/
โ”œโ”€โ”€ ๐Ÿ“ cli/                     # Command-line interface
โ”‚   โ”œโ”€โ”€ commands/               # One module per command (or command family)
โ”‚   โ”‚   โ”œโ”€โ”€ init.py            # Guided setup (config, monitoring user, test)
โ”‚   โ”‚   โ”œโ”€โ”€ ping.py            # Connectivity + client/server versions
โ”‚   โ”‚   โ”œโ”€โ”€ uptime.py          # Uptime command
โ”‚   โ”‚   โ”œโ”€โ”€ connections.py     # Connections command
โ”‚   โ”‚   โ”œโ”€โ”€ replication.py     # Replication command
โ”‚   โ”‚   โ”œโ”€โ”€ slowqueries.py     # Slow queries command
โ”‚   โ”‚   โ”œโ”€โ”€ latency.py         # Latency command
โ”‚   โ”‚   โ”œโ”€โ”€ longrunning.py     # Long-running queries command
โ”‚   โ”‚   โ”œโ”€โ”€ openfiles.py       # Open files command
โ”‚   โ”‚   โ”œโ”€โ”€ security.py        # mysql.user security audit
โ”‚   โ”‚   โ”œโ”€โ”€ sql.py             # Arbitrary scalar SQL command
โ”‚   โ”‚   โ”œโ”€โ”€ ratios.py          # Hitrate family: threadcache, querycache,
โ”‚   โ”‚   โ”‚                      #   keycache, tablecache, bufferpool,
โ”‚   โ”‚   โ”‚                      #   tablelocks, indexusage, tmpdisktables
โ”‚   โ”‚   โ””โ”€โ”€ counters.py        # Rate family: querycacheprunes,
โ”‚   โ”‚                          #   bufferpoolwaits, logwaits
โ”‚   โ”œโ”€โ”€ decorators.py          # Common CLI decorators
โ”‚   โ””โ”€โ”€ handlers.py            # Shared command execution path
โ”œโ”€โ”€ ๐Ÿ“ core/                    # Core business logic
โ”‚   โ”œโ”€โ”€ config.py              # Configuration handling
โ”‚   โ”œโ”€โ”€ connection.py          # Direct / SSH tunnel connector
โ”‚   โ”œโ”€โ”€ mysql_client.py        # MySQL query client
โ”‚   โ”œโ”€โ”€ provisioning.py        # Monitoring-user creation (init)
โ”‚   โ”œโ”€โ”€ status.py              # SHOW GLOBAL STATUS/VARIABLES helpers
โ”‚   โ”œโ”€โ”€ exceptions.py          # Custom exceptions
โ”‚   โ”œโ”€โ”€ models.py              # Dataclasses, TypedDicts, Protocols
โ”‚   โ”œโ”€โ”€ nagios.py              # Nagios plugin framework
โ”‚   โ””โ”€โ”€ logging_config.py      # Logging configuration
โ”œโ”€โ”€ ๐Ÿ“ services/                # One service per check
โ”‚   โ”œโ”€โ”€ ping_service.py        # โ€ฆone file per command aboveโ€ฆ
โ”‚   โ”œโ”€โ”€ ratio_service.py       # RATIO_SPECS table + shared hitrate logic
โ”‚   โ”œโ”€โ”€ counter_service.py     # COUNTER_SPECS table + shared rate logic
โ”‚   โ””โ”€โ”€ security_service.py    # Account audit rules
โ””โ”€โ”€ ๐Ÿ“ tests/                   # Comprehensive test suite
    โ”œโ”€โ”€ unit/                   # Unit tests
    โ”œโ”€โ”€ integration/            # CLI integration tests
    โ”œโ”€โ”€ e2e/                    # End-to-end tests (real local MySQL server)
    โ””โ”€โ”€ fixtures/               # Test fixtures (mock client, datasets)

Key Design Principles

  • ๐ŸŽฏ Single Responsibility - Each module has one clear purpose
  • ๐Ÿ”Œ Dependency Injection - Services depend on a client Protocol, trivially mockable
  • ๐Ÿงช Testable - The default test suite runs without any MySQL server; the e2e suite drives the real binary against a local one
  • ๐Ÿ“ˆ Extensible - Adding a check = one service + one command file
  • ๐Ÿ”’ Secure - No secrets in code, key-based SSH authentication

๐Ÿงช Development

Development Setup

# Clone repository
git clone https://github.com/lduchosal/check_mysql.git
cd check_mysql

# Install with PDM (creates .venv)
pdm install
pdm install -G dev

Code Quality Tools

# The full quality pipeline (format, lint, types, docstrings, tests, gate)
pdm run check

# Individual steps
pdm run lint          # ruff
pdm run typecheck     # pyright (strict)
pdm run test          # pytest + coverage (no MySQL server needed)
pdm run test-e2e      # end-to-end against the local server (check_mysql.ini)
pdm run metrics       # local quality metrics snapshot

See doc/code-quality.md for the full quality standard (blocking gate + best-ever ratchet).

Building & Publishing

# Quality checks only
sh publish.sh --quality

# Full pipeline: quality, local e2e, SonarCloud gate, bump, build, publish, tag
sh publish.sh --patch          # or --minor / --major

# CI mode (skips the end-to-end suite, which needs the local MySQL server)
sh publish.sh --ci --quality

๐Ÿ” Output Examples

Successful Check

MYSQL OK - Server up for 10 days, 0:00:00 (864000 seconds) | uptime=864000s;3600:;300:

Ping (versions)

MYSQL OK - client PyMySQL 2.2.8, server 8.4.9 | ping=0.25ms

Security Audit

MYSQL OK - 0 risky accounts out of 16 audited | security=0;0;5

Warning State

MYSQL WARNING - Connections: 130/151 (86.09% of max_connections) | connections=86.09%;80;95

Critical State (replication stopped)

MYSQL CRITICAL - Replication threads stopped (IO: No, SQL: Yes): error connecting to source

Unknown State

UNKNOWN: Configuration file not found: check_mysql.ini

๐Ÿ”ง Troubleshooting

Common Issues

Issue Solution
Access denied Verify the monitoring user credentials and grants
SSH tunnel failures Check bastion reachability, key permissions (600) and known_hosts
Replication UNKNOWN The target is not a replica โ€” point at the right server
Configuration Issues Validate config file syntax and search paths

Debug Mode

# Maximum verbosity (SQL queries, tunnel lifecycle, method traces)
check_mysql replication -vvv

# Check a specific configuration
check_mysql uptime -c /path/to/config.ini -vv

๐Ÿ“Š Exit Codes

Code Status Description
0 OK Value within acceptable range
1 WARNING Value exceeds warning threshold
2 CRITICAL Value exceeds critical threshold, or replication stopped
3 UNKNOWN Error occurred during execution

๐Ÿค Contributing

We welcome contributions! Here's how to get started:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Development Guidelines

  • Follow PEP 8 style guide
  • Add tests for new features
  • Update documentation as needed
  • pdm run check must be green before submitting

๐Ÿ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

๐Ÿ™ Acknowledgments


๐Ÿ’– Sponsor

If this project helps you, consider supporting its development:

GitHub Sponsors

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

check_mysql_nagios-0.2.3.tar.gz (69.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

check_mysql_nagios-0.2.3-py3-none-any.whl (52.2 kB view details)

Uploaded Python 3

File details

Details for the file check_mysql_nagios-0.2.3.tar.gz.

File metadata

  • Download URL: check_mysql_nagios-0.2.3.tar.gz
  • Upload date:
  • Size: 69.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: pdm/2.28.0 CPython/3.12.3 Darwin/25.5.0

File hashes

Hashes for check_mysql_nagios-0.2.3.tar.gz
Algorithm Hash digest
SHA256 e05e3b3cff8902a2c08eba079c6a0ee4c35c4b01c1d32baadd36516f19ebb912
MD5 f4d03f440dca3f3b11526cc2c53dc81d
BLAKE2b-256 bfcb2c7aae64df26c934cd8660006d3b98f019c44e7bdea85c6df4a4c7512156

See more details on using hashes here.

File details

Details for the file check_mysql_nagios-0.2.3-py3-none-any.whl.

File metadata

File hashes

Hashes for check_mysql_nagios-0.2.3-py3-none-any.whl
Algorithm Hash digest
SHA256 b17dec854250900588f7c03735826534232f4405f6991e6255b45dde96cfa12c
MD5 b7dd8df8b3883322fc80e7888278ca26
BLAKE2b-256 c224ef395e9b765ec984f6a481838d98c6893915cd1cb08fdbc97f2fe291fcd9

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page