Redis Health Check: PING, Compose and the Lies
A redis health check built on PING alone misses eviction, replication and failed saves. Here is the compose config and the probe that catch them.
A redis health check means one of two things. The first is a container healthcheck that decides when the service is ready, and it is a one-line redis-cli ping. The second is an application probe that decides whether Redis can still do useful work. That one needs more, because PING keeps answering long after writes have stopped succeeding.
What a redis health check can prove, and what it cannot
PING proves the process is listening, has finished loading, and is not blocked on a script. It proves nothing about memory pressure, replication, persistence or eviction. Those failures are silent at the protocol level. A dashboard full of green containers can sit on top of a cache that has been rejecting writes for an hour.
Treat the two layers separately. The container check answers "may traffic start flowing here". The application probe answers "is the data path still working". Mixing them produces restart loops during an ordinary background save.
The three answers redis-cli PING can give
Run it and read the reply rather than the exit status:
$ redis-cli -h 127.0.0.1 -p 6379 ping
PONG
$ redis-cli ping
(error) NOAUTH Authentication required.
$ redis-cli ping
(error) LOADING Redis is loading the dataset in memory
PONG is the only healthy answer. NOAUTH means requirepass is set and the client did not authenticate, which is a configuration bug on your side, not an outage. LOADING appears while an instance replays an RDB or AOF file after a restart, and it can last minutes on a large dataset. A BUSY reply means a Lua script is still running and the server will not serve anything else until it finishes or is killed.
The detail that trips people up is the exit code. redis-cli exits 0 and prints the error text, so any check that only tests the exit status will call a NOAUTH error a success. The PING command reference lists the replies in full.
A docker-compose healthcheck that waits for a real PONG
Wrap the command in a shell and grep for the string. This is the version that behaves during a slow restore:
services:
redis:
image: redis:7-alpine
command: ["redis-server", "--appendonly", "yes", "--maxmemory", "512mb"]
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep -q PONG"]
interval: 10s
timeout: 3s
retries: 5
start_period: 40s
api:
build: .
depends_on:
redis:
condition: service_healthy
start_period is the field most compose files forget. Without it, a restore that takes longer than interval * retries marks the container unhealthy and the orchestrator restarts it, which starts the restore again from the beginning. We prefer a start period roughly twice the worst restore you have measured. The compose healthcheck reference documents every field.
Does PING prove Redis can accept writes?
No. An instance at its maxmemory limit with the noeviction policy answers PING normally and rejects every SET with OOM command not allowed when used memory > 'maxmemory'. A read-only replica answers PING normally and rejects writes with READONLY You can't write against a read only replica. Both look perfectly healthy to a container check.
If your application writes to Redis, probe with a write:
redis-cli set healthprobe:$(date +%s) 1 EX 30
A failure here is real and actionable. Keep the key short-lived so the probe cannot itself add to the memory pressure it is meant to detect.
Where you put that write matters. Running it from the application, inside the same endpoint that already answers for the database, keeps one story about readiness in one place. Running it from a sidecar gives you a cleaner signal but a second thing to deploy and forget about. Either way, log the error string rather than a boolean. OOM and READONLY point at completely different fixes, and a bare false tells the on-call engineer neither.
Reading INFO for the failures PING hides
Four fields cover most of what goes wrong quietly. Poll them from a metrics job rather than a health endpoint. INFO is heavier than PING:
| Field | Healthy value | What a bad value means |
|---|---|---|
loading | 0 | The instance is still replaying its dataset and will reject commands |
rdb_last_bgsave_status | ok | The last snapshot failed, often out of disk or fork memory |
aof_last_write_status | ok | Appends are failing, so durability is already gone |
master_link_status | up | This replica lost its link and is serving stale data |
master_link_status only appears on replicas, and it is the field worth alerting on first. A detached replica keeps serving reads happily. The replication documentation explains how long stale data can be served, depending on your configuration.
Two more fields are worth graphing without alerting on them. used_memory against maxmemory tells you how much headroom is left before eviction starts. connected_clients against your pool sizes catches a leak weeks before it becomes an outage. Neither belongs in a readiness probe, because neither says anything about whether this instance can serve the next command.
Checking the port from outside the host
Container checks and metrics jobs both die with the machine they run on. Something outside the blast radius has to confirm the listener is still there. Managed providers often expose a public TLS endpoint. Where yours does, a TCP check covers it:
curl -X POST https://uptimeroom.com/api/v1/monitors \
-H "Authorization: Bearer urm_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "redis listener",
"type": "tcp",
"target": "cache.example.com",
"port": 6379,
"intervalSec": 60
}'
An external TCP probe confirms a handshake and nothing above it. Eviction and replication state sit above that line, so they need a query from inside your network. Where Redis sits on a private network, monitor the application endpoint that depends on it instead, in the same style as a Django health endpoint. Probe egress ranges for firewall rules are listed on the probe IP page, and the monitor fields are documented in the API reference.
Three ways this redis health check goes wrong
Testing the exit code instead of the output. NOAUTH and LOADING both exit 0. Grep for PONG or you are checking that the binary exists.
Alerting on a single failed check. Redis pauses for fork during a background save, and a probe that lands in that window sees a timeout. Require agreement from two probes, as cross-region quorum sets out, before anyone is paged.
Using the same probe for readiness and for alerting. Readiness should be fast and forgiving. The alerting probe should be strict and should write. One endpoint cannot be both.
Keep the layers apart and a redis health check stops being decorative. Use PING for container readiness and a short-lived SET for the data path. Read the INFO fields for silent failures, and put an external check on the listener itself. You can put the outside layer in place in a few minutes on the free plan, which covers 10 monitors at a one-minute interval as of September 2026.
FAQ
Run redis-cli ping and confirm the reply is the literal string PONG rather than checking the exit code, which is zero even for errors. For anything beyond liveness, write a short-lived key with SET and an expiry, then read the loading, rdb_last_bgsave_status and master_link_status fields from INFO on a slower schedule.
Use a shell form so the output is inspected: test set to CMD-SHELL with redis-cli ping piped into grep for PONG. Add an interval of about ten seconds, a short timeout, a few retries, and a start_period long enough to cover a full dataset restore, otherwise the container is restarted mid-recovery.
Redis replies with a LOADING error while it replays its RDB or AOF file into memory, and that can take minutes on a large dataset. If the health check has no start_period, the container is marked unhealthy and restarted before loading finishes, which begins the restore again and creates a loop.
No. An instance that has hit its maxmemory limit under the noeviction policy still answers PING while rejecting every write with an OOM error, and a read-only replica does the same with a READONLY error. Only an actual SET command proves the write path works.
Only when the provider already offers a public TLS endpoint, and then a TCP check confirms the listener and nothing above it. For a Redis on a private network, leave the port closed and point the external probe at the application endpoint that reads and writes the cache instead.
Related reading
- Django Health Check: Endpoint, Probes and Alerts - A django health check should verify the database, cache and broker, answer 503 when one is gone, and be watched from outside your own cluster.
- Blackbox Monitoring: What the Outside View Sees - Blackbox monitoring tests a service from outside with no access to its internals, which is how you catch DNS, TLS and routing failures.