A Nagios plugin for monitoring MySQL servers, directly or through an SSH tunnel
Project description
๐ฌ Check MySQL
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_Sourceagainst 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 themysql_no_loginplugin โ 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@localhostordebian-sys-maint@localhostare 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:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Development Guidelines
- Follow PEP 8 style guide
- Add tests for new features
- Update documentation as needed
pdm run checkmust be green before submitting
๐ License
This project is licensed under the MIT License - see the LICENSE file for details.
๐ Acknowledgments
- Built with nagiosplugin framework
- Uses PyMySQL for MySQL connectivity
- Uses sshtunnel for bastion traversal
- Powered by Click for CLI interface
โญ Star this repository if you find it useful!
๐ Report Bug โข ๐ก Request Feature โข ๐ Documentation
๐ Sponsor
If this project helps you, consider supporting its development:
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file check_mysql_nagios-0.2.1.tar.gz.
File metadata
- Download URL: check_mysql_nagios-0.2.1.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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
35cb10143f9e2a90c2e5d31ed73c9c42e625d5ab5abb077daa55dc6708b3c1bd
|
|
| MD5 |
eb7b34a224ae211c7ab96516ec55241b
|
|
| BLAKE2b-256 |
79230eb19ad042cddf6594df0017eceea8d0add806b1e92e2092899091aedfe2
|
File details
Details for the file check_mysql_nagios-0.2.1-py3-none-any.whl.
File metadata
- Download URL: check_mysql_nagios-0.2.1-py3-none-any.whl
- Upload date:
- Size: 50.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: pdm/2.28.0 CPython/3.12.3 Darwin/25.5.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
86c81661931f1868efecc22f3787bbed3fb86d0e320f0aa39f6b6e841e8ee019
|
|
| MD5 |
e1e9779942b09c0aad8911d65fcb53fd
|
|
| BLAKE2b-256 |
319ef48a25a3dcbfa004fba9bf2985ae402865bf79aa1248bde2a2d5caca1512
|