
When a database becomes unreachable, saturated, read-only, or too slow, an otherwise healthy application can fail. A green process check is not enough: production database monitoring must prove that clients can connect, a representative query succeeds, capacity remains available, and replicas are current enough for their workload.
Database failures are also among the most expensive failures. Every query fails. Every page that needs data fails. Revenue, session data, order processing — all of it stops until the database is back.
Yet most teams monitor their databases less rigorously than their web servers. They assume the database is running because the application was working an hour ago. They find out about database problems the same way their users do.
This guide covers how to monitor MySQL, PostgreSQL, and Redis effectively — from connectivity checks to performance monitoring — and how to get alerted before your users notice a problem.
Why Database Monitoring Is Different
Databases fail differently from web applications. Where a web app might return a 500 error, a database failure often manifests as:
- Connection timeout — The application hangs waiting for a database connection that never comes
- Connection refused — The database process isn't running or the port is blocked
- Authentication failure — Credentials changed or the user was revoked
- Connection pool exhaustion — Too many concurrent connections, new ones are rejected
- Replication lag — Read replicas are serving stale data, causing subtle data inconsistencies
- Disk full — The database can't write new data, causing partial failures
Many of these failure modes don't produce obvious errors immediately — they cause slowness and intermittent failures that escalate into full outages over time. Early detection is everything.
A Practical Database Monitoring Stack
Complete database monitoring has three layers:
Layer 1: Network reachability (private or allowlisted) Can an approved monitoring agent reach the database listener? Keep database ports private; run TCP checks from inside the VPC or through a tightly allowlisted path rather than exposing MySQL, PostgreSQL, or Redis to the public internet.
Layer 2: Application-layer monitoring (via health endpoints) Does your application successfully connect to the database? Does a test query succeed? This layer sits one level up and catches authentication failures, permission issues, and query-level problems.
Layer 3: Performance and capacity monitoring (internal) How many connections are active? What's the query latency? Is replication keeping up? Is disk usage growing? This layer requires internal access to database metrics.
Use all three layers together. A TCP probe can show that a listener accepts connections, but only an authenticated query detects revoked credentials, read-only transitions, exhausted connection slots, or a database that accepts sockets but cannot complete work.
MySQL Monitoring
MySQL is the world's most widely deployed open-source relational database. It's the "M" in the LAMP and LEMP stacks and runs under most WordPress, Laravel, and PHP applications.
TCP port check
MySQL listens on port 3306 by default. The simplest database health check is a TCP connection test to that port:
TCP check: your-db-host.com:3306
If the TCP check fails, MySQL is either not running or unreachable due to a firewall change. This check runs from outside your database host and detects:
- MySQL process stopped (crash, OOM kill, manual stop)
- Host unreachable (network issue, instance shutdown)
- Firewall or security group change blocking the port
- Port binding failure after a restart
Configure in Webalert: Create a TCP monitor with your database host and port 3306.
Application health endpoint
Rather than connecting directly to MySQL for monitoring, expose a health endpoint in your application that tests the database connection:
# FastAPI example
from sqlalchemy import text
@app.get("/health/db")
async def database_health():
try:
async with engine.connect() as conn:
await conn.execute(text("SELECT 1"))
return {"status": "ok", "database": "connected"}
except Exception as e:
return JSONResponse(
status_code=503,
content={"status": "error", "database": str(e)}
)
// Express + mysql2 example
app.get('/health/db', async (req, res) => {
try {
await pool.execute('SELECT 1');
res.json({ status: 'ok', database: 'connected' });
} catch (err) {
res.status(503).json({ status: 'error', database: err.message });
}
});
Monitor this endpoint with an HTTP check that validates the response body contains "status":"ok". This catches:
- Successful TCP connection but failed authentication
- MySQL running but application credentials revoked
- Database-level permission issues
- MySQL running but accepting no new connections (max_connections reached)
MySQL-specific failure modes
max_connections exceeded
MySQL has a configurable limit on simultaneous connections (default: 151). When it's reached, new connections are rejected with Too many connections. Your application starts returning errors even though MySQL is "running."
Detection: Your application health endpoint returns 503. TCP check passes (process is running) but application check fails.
InnoDB buffer pool exhaustion When MySQL runs low on memory, query performance degrades significantly before queries start failing outright.
Detection: Response time increase on your application endpoints, visible in response time monitoring before the full outage.
Replication lag (with read replicas) If your application reads from replicas, replication lag means reads return stale data. This causes subtle data inconsistency bugs rather than hard failures.
Detection: Application-level checks that verify recently written data is readable from the replica.
PostgreSQL Monitoring
PostgreSQL is the most feature-rich open-source relational database and is increasingly the default choice for new applications. It's known for correctness, reliability, and extensibility.
TCP port check
PostgreSQL listens on port 5432 by default:
TCP check: your-db-host.com:5432
Same as MySQL — this catches process crashes, host unreachability, and firewall changes.
Application health endpoint
# FastAPI + asyncpg example
import asyncpg
@app.get("/health/db")
async def database_health():
try:
conn = await asyncpg.connect(DATABASE_URL)
await conn.fetchval("SELECT 1")
await conn.close()
return {"status": "ok", "database": "connected"}
except Exception as e:
return JSONResponse(
status_code=503,
content={"status": "error", "database": str(e)}
)
// Go + pgx example
http.HandleFunc("/health/db", func(w http.ResponseWriter, r *http.Request) {
err := pool.QueryRow(context.Background(), "SELECT 1").Scan(nil)
if err != nil {
w.WriteHeader(503)
json.NewEncoder(w).Encode(map[string]string{"status": "error", "database": err.Error()})
return
}
json.NewEncoder(w).Encode(map[string]string{"status": "ok", "database": "connected"})
})
PostgreSQL-specific failure modes
Connection pool exhaustion (max_connections)
PostgreSQL has a max_connections setting (default: 100). Unlike MySQL, PostgreSQL spawns a new process per connection, making connection exhaustion particularly resource-intensive.
Common solution: Use a connection pooler like PgBouncer between your application and PostgreSQL. Monitor PgBouncer's health separately.
Detection: Application health endpoint returns 503 with connection error. TCP check passes.
Write-ahead log (WAL) disk space If PostgreSQL's WAL fills the disk, writes fail. PostgreSQL enters a read-only mode or crashes.
Detection: Application health endpoint fails for write operations. Disk space monitoring (if available) shows high usage.
Autovacuum blocking Long-running transactions prevent autovacuum from cleaning up dead tuples, causing table bloat and eventually query slowdowns.
Detection: Response time increase on database-heavy endpoints, caught by response time monitoring.
Streaming replication lag PostgreSQL streaming replication can fall behind under heavy write loads or network issues. Read replicas serve stale data.
Detection: Application-level data consistency checks on read replicas.
Redis Monitoring
Redis is an in-memory data store used for caching, session management, queues, pub/sub, and real-time leaderboards. It's not a persistent primary database for most applications, but losing Redis often makes the application unusable anyway — sessions invalidate, caches miss, queues stop processing.
TCP port check
Redis listens on port 6379 by default:
TCP check: your-redis-host.com:6379
This catches Redis process crashes and network unreachability.
Application health endpoint
# FastAPI + aioredis example
import aioredis
@app.get("/health/cache")
async def cache_health():
try:
redis = await aioredis.from_url(REDIS_URL)
await redis.ping()
await redis.close()
return {"status": "ok", "cache": "connected"}
except Exception as e:
return JSONResponse(
status_code=503,
content={"status": "error", "cache": str(e)}
)
Redis-specific failure modes
Memory limit reached (maxmemory)
Redis has a configurable maxmemory limit. When it's reached, Redis's eviction policy kicks in. With noeviction policy, write commands fail with an error. With volatile or allkeys eviction, keys start being evicted unexpectedly.
Detection: Application errors on Redis write operations, application health endpoint reports cache errors.
RDB snapshot or AOF write failure If Redis is configured for persistence and fails to write snapshots or append-only log entries (e.g., disk full), it may refuse writes or crash.
Detection: Redis process crash detected by TCP check failing.
Redis running but application can't authenticate
If Redis is configured with a password (requirepass) and the application's password is wrong or expired, connections fail authentication.
Detection: Application health endpoint returns 503 with authentication error. TCP check passes.
Keyspace eviction surprise Under memory pressure, Redis evicts keys that your application expects to exist. Sessions disappear, caches miss unexpectedly.
Detection: Application-level monitoring that tracks cache hit rates and session validity.
What to Monitor: The Database Monitoring Checklist
TCP connectivity (all databases)
- MySQL port 3306
- PostgreSQL port 5432
- Redis port 6379
- Any other database ports in use
Choose an interval and failure window from the service's recovery objective. Fast user-facing paths may justify a one-minute probe; lower-priority stores can use a longer interval. Alert only after enough evidence to exclude a single network sample.
Application health endpoints
-
/health/db— Tests active database query (SELECT 1 or equivalent) -
/health/cache— Tests Redis ping -
/healthor/readyz— Combined check for all dependencies
Use content validation: verify response contains "status":"ok", not just HTTP 200.
Response time
- Track response time on database-heavy endpoints
- Alert when sustained response time exceeds 2-3x baseline
- This catches degradation (max connections, bloat, slow queries) before full outage
SSL certificates for database connections
If your database uses SSL/TLS for connections (mandatory for cloud-managed databases like RDS, Cloud SQL, Azure Database):
- Monitor certificate expiry for your database SSL cert
- Alert at 30 and 7 days before expiry
Heartbeat for database-dependent jobs
If you have jobs that read/write the database on a schedule (backups, ETL, data cleanup):
- Set up heartbeat monitoring for each critical job
- Alert if the heartbeat is missed within the expected window
Alerting Strategy for Database Issues
Database failures range from a degraded analytics replica to a complete transactional outage. Page according to user impact, not merely because a database metric crossed a threshold:
Immediate (page on-call):
- TCP check fails (database unreachable)
- Application health endpoint returns 503
- Response time >5x baseline sustained for 2+ minutes
Urgent (alert team channel):
- Response time 2-3x baseline sustained
- Heartbeat missed for database-dependent jobs
Informational:
- SSL certificate expiring within 30 days
How Webalert Monitors Databases
Webalert provides the external monitoring checks your database needs:
- TCP port monitoring — Check that MySQL (3306), PostgreSQL (5432), and Redis (6379) ports accept connections, every minute
- HTTP health endpoint monitoring — Check your application's
/health/dband/health/cacheendpoints with content validation - Response time tracking — Detect gradual database performance degradation before it becomes an outage
- SSL certificate monitoring — Track database SSL cert expiry for cloud-managed databases
- Heartbeat monitoring — Verify database backups and ETL jobs complete on schedule
- Multi-region checks — Confirm your database is reachable from multiple locations (useful for cloud databases with network restrictions)
- Fast alerting — Slack, Discord, Microsoft Teams, SMS, email — alerts to whoever is on-call when a database check fails
- On-call scheduling — Route database alerts to the DBA or backend engineer on rotation
Your database is the foundation your application is built on. Make sure someone is watching it.
See features and pricing for the full details.
Primary Documentation
- PostgreSQL cumulative statistics views — connections, activity, replication, I/O, and progress views.
- MySQL Performance Schema — statement, wait, lock, and replication instrumentation.
- Redis
INFOcommand — memory, clients, persistence, replication, keyspace, and latency statistics.
Use the versioned documentation for the database release you actually operate; metric names and semantics can change between major versions.
Summary
- Page on impact, not labels — a failed primary on the write path is critical; a lagging non-serving replica may not be.
- TCP port checks are your first line of defense — they catch process crashes, network issues, and firewall changes within minutes.
- Application health endpoints add depth — they catch authentication failures, connection pool exhaustion, and query-level problems that TCP checks miss.
- Redis failures are often overlooked — losing a cache layer often makes the application as unusable as losing the primary database.
- Monitor response time as an early warning signal — database degradation shows up in response time before it causes outright failures.
- Layer your alerts: TCP failure → immediate page. Response time degradation → urgent channel alert. SSL/heartbeat → informational.
The database doesn't need to be completely down to cause serious problems. Good monitoring catches the warning signs early.