← Knowledge hub
Tutorialby Uptime Room· September 24, 2026· 6 min read

MongoDB Health Check: Ping Is Not Enough

A mongodb health check built on ping misses the failure that matters: a replica set with no writable primary answers every probe and rejects every write.

A mongodb health check is only useful if you know which sentence each command actually proves. A ping proves a process answered on a socket. It says nothing about whether a primary has been elected, and a replica set with no primary rejects every write while every probe stays green.

What a mongodb health check has to prove

Four different claims, four different commands. Most setups run the weakest one and treat the result as an answer to all four.

CommandProvesSays nothing about
db.adminCommand({ping: 1})The server answeredAuth, election state, writes
db.hello()Whether this node is a writable primaryWhether a write concern can be met
rs.runCommand({replSetGetStatus: 1})Member states and election healthWhat your application can reach
An upsert with w: "majority"Writes commit and acknowledgeRead performance

The ping command deserves its reputation as the cheap option, but note what MongoDB documents about it. Ping does not require authentication. A probe using it returns success even when the credentials your application uses are wrong or have been revoked.

Why does a passing ping still mean writes are failing?

Because a mongod that answers is not the same thing as a replica set that can commit. When a primary steps down and an election stalls, every member stays up and responsive. Reads against secondaries keep working. Writes fail immediately with a not-primary error, and any probe that only checks for a response reports a healthy database.

The hello command is the direct answer. It returns isWritablePrimary as a boolean, which is exactly the claim a write-path probe needs to make. For the wider view, replSetGetStatus reports every member's state and the optime each one has reached.

We tested this against a three-member set with the primary's network path blocked. The ping-based container probe stayed healthy for the full eleven minutes it took the set to settle, while the application logged write failures throughout.

A Compose healthcheck that waits for a writable primary

The point of a container healthcheck is to stop dependent services starting too early. For a replica set, "early" means before the initial election has completed, not before the process is listening.

services:
  mongo:
    image: mongo:8
    command: ["--replSet", "rs0", "--bind_ip_all"]
    healthcheck:
      test: ["CMD-SHELL", "mongosh --quiet --eval 'quit(db.hello().isWritablePrimary ? 0 : 1)'"]
      interval: 10s
      timeout: 5s
      retries: 12
      start_period: 40s
  api:
    build: .
    depends_on:
      mongo:
        condition: service_healthy

Three details matter. Use quit() with an explicit exit code, because mongosh exits zero on a successful connection even when the expression evaluated false. Set start_period generously, since a first-boot election plus any seed data can take well over thirty seconds. Keep retries high enough to cover a normal failover, or a routine election will restart your application stack. The Compose healthcheck reference covers how the fields interact.

The readiness route your application should expose

A container probe runs next to the database with the database's own credentials. Your application connects across the network with different credentials, through a driver, using a connection pool. Those are separate failure modes, and after a rotated secret only the second one is broken.

Expose a route that borrows the real pool and proves the write path end to end.

// GET /readyz
const hello = await client.db().admin().command({ hello: 1 });
if (!hello.isWritablePrimary) {
  return res.status(503).json({ mongo: 'no writable primary' });
}

await client.db('app').collection('_health').updateOne(
  { _id: 'probe' },
  { $set: { at: new Date() } },
  { upsert: true, writeConcern: { w: 'majority', wtimeoutMS: 2000 } }
);

return res.status(200).json({ mongo: 'ok' });

The wtimeoutMS is doing real work. Without it, a write concern that cannot be satisfied will block the request until the driver's own timeout, turning a readiness probe into a source of hung connections.

Keep the route cheap and uncached. We prefer separate liveness and readiness routes, so a brief election can pull the instance out of the load balancer without the orchestrator deciding to restart it.

Watching the deployment from outside

Both layers above run inside your network. Neither one fires if the whole host, the security group or the region is the problem. An external check closes that gap.

curl -X POST https://uptimeroom.com/api/v1/monitors \
  -H "Authorization: Bearer urm_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "app readiness",
    "type": "keyword",
    "target": "https://example.com/readyz",
    "intervalSec": 60,
    "regions": ["london", "singapore"],
    "keyword": "\"mongo\":\"ok\"",
    "keywordMatch": "contains",
    "expectedStatus": [200],
    "alertAfterCycles": 2
  }'

Watch the readiness route rather than port 27017 wherever you can, because the route exercises credentials, the pool and the write concern. If the port genuinely needs watching, a tcp monitor works, but only allowlist the ranges on the probe IPs page rather than opening the database to the internet. The reasoning behind checking a port at all is in port monitoring.

Numbers that live only inside the deployment

Replication lag is the obvious one. The gap between the primary's optime and each secondary's optime shows up in replSetGetStatus and nowhere else. A secondary hours behind is a real incident, and every primary-side probe reports it as fine.

Give each member its own alert on that gap. A set that looks healthy in aggregate can be one node away from losing its ability to elect anything. The warning sign is lag, not a failed command.

The same applies to the WiredTiger cache hit ratio, the count of open connections against the server limit, and any operation that has been running for minutes. Put those in a scheduled diagnostic job, and wire the job itself to an inbound heartbeat so a silent failure of the script gets noticed.

The split is the same one described in the Postgres equivalent. Reachability and correctness are visible from outside. Saturation is not.

Common mistakes

Using ping as the whole mongodb health check. It skips authentication and ignores election state, which are the two things most likely to be wrong.

Grepping mongosh output for a string. Locale, quiet mode and version changes all alter the text. Exit codes are stable and string output is not.

Probing a secondary and reporting the set as healthy. A secondary answers happily while the set has no primary. Assert on isWritablePrimary from the node you expect to write to.

Restarting on a failed readiness check. An election is a normal event lasting seconds. Liveness should tolerate it, and only readiness should pull traffic away during it.

Sharing the application's credentials with the container probe. The container probe should have its own role with the narrowest grant that still answers the question. Reusing the application user means a permissions mistake shows up as a healthy container and a broken service.

Caching the readiness response. A handler that remembers its last answer for sixty seconds will report a healthy database for a full minute after the set loses its primary. That is precisely the minute the alert was for.

A layered mongodb health check takes about twenty minutes to build. Use an exit-code Compose probe that waits for a writable primary, a readiness route that performs a real majority write, and one external check in front of both. Together they cover the failure a ping quietly misses. The free plan includes 10 monitors on a 1-minute interval as of September 2026, so you can add the external layer free and compare the 30-second intervals later.

FAQ

How do I add a MongoDB health check in Docker Compose?

Add a healthcheck block that runs mongosh and exits with an explicit code, for example quit based on db.hello().isWritablePrimary. Give it a start_period of forty seconds or more so the first election can finish, and enough retries to survive a routine failover. Dependent services then wait on depends_on with condition service_healthy rather than starting against a set that cannot accept writes.

How do you check MongoDB health?

Run ping to confirm the server answers, hello to confirm the node is a writable primary, and replSetGetStatus to see every member's state and replication position. For the strongest signal, perform a small upsert with a majority write concern and a write timeout. Each command proves a different claim, and the cheapest one proves the least.

Does the ping command require authentication in MongoDB?

No. Ping is one of the commands that runs without authentication, which is why a ping-based probe returns success even when the credentials your application uses have been revoked or rotated incorrectly. That makes it fine as a liveness signal and unsuitable on its own as evidence that the database is usable by your service.

How do I check if port 27017 is open?

From another machine, a TCP connection attempt to port 27017 tells you whether the socket accepts connections. That answers reachability only, not whether authentication works or whether a primary exists. Keep the port closed to the public internet and monitor an application readiness route instead, since that path exercises credentials, the connection pool and the write concern.

Can an external check detect MongoDB replication lag?

Not directly, because the optime of each member lives inside the deployment. The workaround is to read the lag in your own readiness handler and expose it as a field in the JSON response, which an external check can then assert on. Otherwise keep a scheduled diagnostic job for lag, connection counts and cache ratios.

Related reading