more than just a 200 status code
A health check that only verifies the web server responds reports a store as healthy even when the database is stuck, search is down, or the message queue is backed up. A well designed health check endpoint verifies real dependencies and stops a load balancer from sending more traffic to a broken node.
Table of Contents
- 1. Why a health check must be more than a ping
- 2. Liveness and readiness: two different questions
- 3. Building a custom health check controller in Magento
- 4. Including the database and Redis in the check
- 5. Verifying search and the message queue without adding load
- 6. Wiring health checks into Kubernetes probes
- 7. Security: securing the health check endpoint
- 8. Avoiding timeouts, caching, and cascade effects
- 9. Health check strategies compared
- 10. Summary
- 11. FAQ
1. Why a health check must be more than a ping
Many Magento installations rely on a simple HTTP call to the homepage to decide whether a node is healthy. The problem: the homepage can be served from the full page cache even when the database behind it has been unreachable for a while. Such a health check keeps reporting green while checkout, which is never cached, already fails for real customers.
A well designed health check instead verifies the dependencies operations actually rely on: the database connection, the Redis cache, the search engine, and the message queue connection. Only once these checks succeed does the health check endpoint report the node as ready for traffic. That precision is the difference between monitoring that catches real outages and monitoring that only creates a false sense of safety.
For production Magento stores running several application servers behind a load balancer, a custom health check is not a nice to have but a prerequisite for clean rolling deployments and automatic failover. Without it, every operator is left choosing between blind trust and manual intervention at every incident.
2. Liveness and readiness: two different questions
A central misunderstanding around health checks is treating liveness and readiness as the same thing. Liveness answers whether the process is even still running and responsive, regardless of whether it can currently serve requests meaningfully. Readiness answers whether the node is currently ready to receive traffic, because all necessary dependencies are reachable.
The distinction becomes concrete when the database is briefly unreachable: the PHP process itself keeps running fine, so liveness stays green. Readiness, on the other hand, must flip to red so the load balancer stops sending new traffic to that node while existing connections drain gracefully. A health check endpoint that blends both concepts risks either unnecessary restarts on liveness failures or blindly routing traffic during readiness problems.
3. Building a custom health check controller in Magento
Magento already ships a basic health_check.php, but it only verifies fundamental availability. For a meaningful health check, a custom controller reachable through a dedicated route and returning structured JSON with the status of every individual dependency pays off. This approach makes problems immediately diagnosable instead of only delivering a binary status.
It matters to keep the controller independent from the regular frontend layout, so a failure in the layout system itself does not take the health check down with it. A direct response without block rendering and without the template engine considerably reduces the surface for bugs in the health check itself.
<?php
declare(strict_types=1);
namespace Mironsoft\Observability\Controller\Health;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\Controller\Result\JsonFactory;
use Mironsoft\Observability\Model\HealthCheck\CheckPool;
/**
* Runs all registered dependency checks and returns a structured JSON result.
*/
class Index implements HttpGetActionInterface
{
/**
* @param JsonFactory $resultJsonFactory Factory for building JSON responses.
* @param CheckPool $checkPool Pool of registered dependency checks.
*/
public function __construct(
private readonly JsonFactory $resultJsonFactory,
private readonly CheckPool $checkPool,
) {
}
/**
* Executes every registered check and returns the aggregated status.
*
* @return ResponseInterface
*/
public function execute(): ResponseInterface
{
$results = [];
$healthy = true;
foreach ($this->checkPool->getChecks() as $name => $check) {
$result = $check->run();
$results[$name] = $result->toArray();
$healthy = $healthy && $result->isHealthy();
}
$resultJson = $this->resultJsonFactory->create();
$resultJson->setHttpResponseCode($healthy ? 200 : 503);
return $resultJson->setData(['status' => $healthy ? 'ok' : 'degraded', 'checks' => $results]);
}
}
4. Including the database and Redis in the check
The database check should not just test an open connection but run a minimally meaningful query, such as reading a store configuration value. A pure connection check misses situations where the connection exists but the database is practically unusable due to locks or replication lag. The health check for the database should also measure response time and flag the node as degraded at noticeably elevated latency, not only at a full timeout.
A similar principle applies to Redis: a plain PING is not enough when Redis is configured as session storage and the specific database index in use is unreachable. The health check should use the same Redis client and configuration as the production session handling, so a configuration error affecting only one cache area actually gets detected.
<?php
declare(strict_types=1);
namespace Mironsoft\Observability\Model\HealthCheck;
use Magento\Framework\App\ResourceConnection;
/**
* Verifies the database connection by running a lightweight, real query
* instead of just checking whether a connection object exists.
*/
class DatabaseCheck implements CheckInterface
{
private const SLOW_THRESHOLD_MS = 200;
/**
* @param ResourceConnection $resourceConnection Magento's DB connection resolver.
*/
public function __construct(private readonly ResourceConnection $resourceConnection)
{
}
/**
* Runs a minimal read query and measures its latency.
*
* @return CheckResult
*/
public function run(): CheckResult
{
$start = microtime(true);
try {
$connection = $this->resourceConnection->getConnection();
$connection->fetchOne('SELECT 1');
} catch (\Throwable $exception) {
return CheckResult::failed('database', $exception->getMessage());
}
$elapsedMs = (microtime(true) - $start) * 1000;
return $elapsedMs > self::SLOW_THRESHOLD_MS
? CheckResult::degraded('database', sprintf('slow response: %.1fms', $elapsedMs))
: CheckResult::healthy('database');
}
}
5. Verifying search and the message queue without adding load
Elasticsearch or OpenSearch can be verified through the cluster health endpoint, which exists precisely for this purpose and requires no expensive search query. A health check that instead runs a real product search generates unnecessary load on the search index and needlessly delays the entire check response, especially with large catalogs.
The message queue, usually RabbitMQ, can be checked through the management API to confirm the relevant queues exist and do not hold an excessive number of unprocessed messages. A health check that only tests the TCP connection to RabbitMQ misses the most common real world problem: a stuck consumer that still receives messages but no longer processes them while the queue keeps filling up.
6. Wiring health checks into Kubernetes probes
In a Kubernetes environment, liveness and readiness translate directly into the corresponding probe types. The liveness probe should stay deliberately lean and only check whether PHP FPM responds, so a temporary database outage does not cause a pod restart that would not fix the problem anyway. The readiness probe, in contrast, calls the full health check endpoint with all dependency checks.
Configuring initialDelaySeconds matters so a freshly started pod is not immediately marked failed while Magento is still warming up OPcache. An overly aggressive probe interval combined with an expensive health check can also become a load source itself, which is why frequency and check depth need to be deliberately balanced.
# Kubernetes deployment snippet for Magento application pods
livenessProbe:
httpGet:
path: /health_check.php
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 3
readinessProbe:
httpGet:
path: /rest/V1/mironsoft-observability/health
port: 8080
initialDelaySeconds: 45
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 2
7. Security: securing the health check endpoint
A health check endpoint that exposes details about internal system state, such as database hostnames or exception messages, is an information leak for attackers. The public variant of the endpoint should return only an aggregated status, while detailed diagnostic data is only visible through an internal route or with valid authentication.
In addition, the health check should not run through the regular Magento middleware with full session initialization, since that adds unnecessary load and potentially opens attack surface through session handling. A lean, dedicated route outside the normal store view resolution is the more robust choice here.
8. Avoiding timeouts, caching, and cascade effects
Every individual check inside the health check endpoint needs its own short timeout. Without this limit, a hanging dependency, say a database waiting on a lock, can block the entire health check call and make the load balancer believe the whole node is dead, when only a single sub check is stuck.
Another important aspect is briefly caching the results, say for two to five seconds, so an aggressively polling load balancer does not re verify every dependency on every request and thereby become a load source for the database itself. The health check must never become the performance problem it is meant to detect.
9. Health check strategies compared
There are several maturity levels for implementing a health check for Magento. The table below ranks common variants by informational value and effort.
| Strategy | Informational value | Risk | Recommendation |
|---|---|---|---|
| Checking the homepage via HTTP | Very low, often cached | Hides database and search outages | Do not use as the sole check |
| TCP port check only | Low | Does not detect stuck processes | Only as liveness, never as readiness |
| Custom aggregated endpoint | High, checks real dependencies | Must be protected against timeouts itself | Recommended standard for Magento |
| Synthetic transaction (real checkout) | Very high, end to end | Creates real orders if done carelessly | Only with test accounts and low frequency |
The pragmatic recommendation for most Magento operations is a custom aggregated endpoint, complemented by an occasional synthetic transaction using dedicated test accounts, to regularly verify real end to end functionality without operational overhead.
Mironsoft
Magento observability, health checks, and operational stability
A health check your load balancer can actually trust?
We build a custom health check endpoint for your Magento store that verifies database, cache, search, and message queue, wires cleanly into Kubernetes probes, and never becomes a source of failure itself.
Dependency checks
Database, Redis, search, and message queue verified individually
Kubernetes integration
Liveness and readiness probes correctly separated and configured
Hardening
Timeouts, caching, and access control for the endpoint itself
10. Summary
A meaningful health check for Magento verifies real dependencies instead of just web server reachability. Database, Redis, search, and message queue must each be assessed individually, with their own timeouts, so a single hanging sub check does not falsely mark the entire node as dead. The clear separation of liveness and readiness prevents unnecessary restarts during temporary dependency problems.
In Kubernetes environments, this structure translates directly into two differently configured probes, while the health check endpoint itself must be protected against abuse and excessive load through caching and access control. Investing this effort once buys reliable rolling deployments and automatic failover, instead of blindly trusting a simple ping.
Building Custom Health Checks for Magento — The key takeaways
Liveness vs. readiness
Liveness checks the process itself. Readiness checks whether all dependencies are currently reachable. Never mix them.
Check real dependencies
Database with a real query, Redis with the production client, search via cluster health, queue via the management API.
Timeouts per check
Every individual check needs its own short timeout, otherwise a hanging dependency blocks the entire check.
Hardening
No internal details in the public response, brief result caching against aggressive pollers.