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
  • ๐ŸŽฏ Multiple Checks - Connectivity ping (client/server versions), uptime, connection usage, replication health, slow queries, query latency, plus the cache-hitrate and load checks backported from check_mysql_health
  • ๐Ÿ“Š 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, 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 โ€ฆ sql block is backported from lausser's check_mysql_health with the same default thresholds. 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
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 or SHUTDOWN โ€” 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.

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).
  • 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): weak-but-not-empty passwords (offline wordlist testing ร  la MySQLTuner only works for unsalted mysql_native_password hashes; caching_sha2_password is salted by design), 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), password expiry, and per-account TLS requirements.

โš™๏ธ 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 next to the plugin, in the working directory, or in /usr/local/etc/nagios / /etc/nagios:

Direct Connection

[mysql]
host = db.example.com
port = 3306
user = nagios
password = secret
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.

Accounts the security audit must not flag (the full criteria and built-in exemptions are described under Security Audit Semantics above) are listed as user@host entries in the optional [security] section:

[security]
allow = backup@10.0.0.5, debian-sys-maint@localhost, app@%

๐Ÿ”ง 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

๐Ÿข 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
}

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
}

๐Ÿ—๏ธ Architecture

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

check_mysql/
โ”œโ”€โ”€ ๐Ÿ“ cli/                     # Command-line interface
โ”‚   โ”œโ”€โ”€ commands/               # Individual command handlers
โ”‚   โ”‚   โ”œโ”€โ”€ ping.py            # Ping command (versions)
โ”‚   โ”‚   โ”œโ”€โ”€ uptime.py          # Uptime command
โ”‚   โ”‚   โ”œโ”€โ”€ connections.py     # Connections command
โ”‚   โ”‚   โ”œโ”€โ”€ replication.py     # Replication command
โ”‚   โ”‚   โ”œโ”€โ”€ slowqueries.py     # Slow queries command
โ”‚   โ”‚   โ””โ”€โ”€ latency.py         # Latency command
โ”‚   โ”œโ”€โ”€ 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
โ”‚   โ”œโ”€โ”€ exceptions.py          # Custom exceptions
โ”‚   โ”œโ”€โ”€ models.py              # Dataclasses, TypedDicts, Protocols
โ”‚   โ”œโ”€โ”€ nagios.py              # Nagios plugin framework
โ”‚   โ””โ”€โ”€ logging_config.py      # Logging configuration
โ”œโ”€โ”€ ๐Ÿ“ services/                # Business services
โ”‚   โ”œโ”€โ”€ ping_service.py        # Ping business logic
โ”‚   โ”œโ”€โ”€ uptime_service.py      # Uptime business logic
โ”‚   โ”œโ”€โ”€ connections_service.py # Connections business logic
โ”‚   โ”œโ”€โ”€ replication_service.py # Replication business logic
โ”‚   โ”œโ”€โ”€ slowqueries_service.py # Slow queries business logic
โ”‚   โ””โ”€โ”€ latency_service.py     # Latency business logic
โ””โ”€โ”€ ๐Ÿ“ 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, SonarCloud gate, bump, build, publish, tag
sh publish.sh --patch

๐Ÿ” 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 1.1.1, server 8.4.0 | ping=1.23ms

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.2.tar.gz (65.1 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.2-py3-none-any.whl (50.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: check_mysql_nagios-0.2.2.tar.gz
  • Upload date:
  • Size: 65.1 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.2.tar.gz
Algorithm Hash digest
SHA256 c81dfdc1a9233d307c9278bab492565fdf2ecfb2507fa1376000f89dcbb77efd
MD5 773241add956975f2ae8c9dd6b4834ad
BLAKE2b-256 50f667bafa60497d54acbfc90ba75081a6593dd1179200a92bde32ba09b22aba

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for check_mysql_nagios-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 0679282fe11808b24d9bd634bd1adeb7c5887d29309a02aeda399b1767acea86
MD5 c6648d8116ef036ef36cfa9f5746b1d6
BLAKE2b-256 a724addab30d90ec6b06ccf76dfda25d7bca7603e23af95ac5f7b2abbe0d2999

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