macpaw/symfony-health-check-bundle
A Symfony bundle that adds configurable health check endpoints to your app, with built-in and custom checks for databases, cache, and other services. Expose readiness and liveness status for monitoring, load balancers, and Kubernetes.
Install the bundle with Composer and register it:
composer require macpaw/symfony-health-check-bundle
// config/bundles.php
return [
// ...
MacPaw\SymfonyHealthCheckBundle\MacPawSymfonyHealthCheckBundle::class => ['all' => true],
];
The first place to look is the bundle’s src/Check directory in the repository — that is the canonical list of bundled checks (Doctrine/DBAL, Redis, RabbitMQ, disk, memory, etc.) and the exact class names and configuration parameters they accept. Mirror those class names in your config rather than guessing from blog posts.
Add a minimal config and hit the endpoint:
# config/packages/macpaw_health_check.yaml
macpaw_health_check:
checks:
- MacPaw\SymfonyHealthCheckBundle\Check\DoctrineCheck
- MacPaw\SymfonyHealthCheckBundle\Check\RedisCheck
curl -i http://localhost:8000/health
# 200 OK + JSON payload when every check passes
# 503 Service Unavailable + JSON payload describing the failing check(s) otherwise
The first real use case is load-balancer or Kubernetes probe wiring: point readinessProbe at /health (deep checks against DB/Redis/queue) and keep livenessProbe shallow — or a separate endpoint — so a transient dependency outage does not cause the orchestrator to restart otherwise healthy pods. Confirm the route name and default path in the bundle’s routing/config before wiring probes, since that is the contract your infrastructure will depend on.
If you use the Redis check, note that as of v2.1.0 the package allows predis/predis ^2.2, so a project already on Predis 2.x no longer needs a Composer conflict dance to install it. Everything in this assessment applies to the 2.x line; v2.1.0 is a maintenance release with no breaking changes.
Start shallow, then go deep. Enable only the checks that map to something your SLA actually cares about. A /health endpoint that pings four external systems on every 2-second probe will itself become a source of load and flakiness. Typical progression: start with disk + memory (self-contained), then add DB, then queue/broker checks once you have timeouts configured on the clients.
Custom checks. Implement the bundle’s check interface (found in the repo alongside the built-in checks) and return a result object rather than throwing. Autoconfiguration via the interface or the bundle’s tag means you usually only need to register the class as a service:
final class S3BucketCheck implements /* bundle check interface */
{
public function __construct(private S3Client $s3) {}
public function check(): HealthCheckResult
{
try {
$this->s3->headBucket(['Bucket' => 'uploads-prod']);
return HealthCheckResult::ok();
} catch (\Throwable $e) {
return HealthCheckResult::fail('S3 unreachable: ' . $e->getMessage());
}
}
}
Return a fail result for expected dependency problems so the endpoint emits 503 (a known-bad state) instead of 500 (an unhandled exception), which is what most probes and dashboards expect to differentiate.
Split endpoints by audience. If the bundle supports multiple check groups (or you register a second controller), expose /health/live (process-only, always fast) separately from /health/ready (dependencies). Kubernetes, ECS, and most LBs let you point liveness and readiness at different paths — use that instead of forcing one endpoint to serve both purposes.
Cache expensive checks. Health endpoints are hit constantly. If the package exposes a result-cache TTL, set something on the order of 5–15 seconds for probes touching DB/Redis and leave the self-contained checks uncached. This bounds the number of real dependency round-trips regardless of how aggressive your LB is.
Pick one Redis client deliberately. RedisCheck supports both the redis extension and Predis; v2.1.0 widened the Predis constraint to ^2.2. The client you install determines how errors surface (exceptions vs. false returns), so assert your check actually fails — stop Redis locally and curl the endpoint — rather than assuming the adapter handles both paths identically.
Integration tips.
HealthCheckController-style endpoint in your app, retire it in favour of this bundle so there is one source of truth./health green during a real outage. If you cache, also alert on the underlying client errors directly.try/catch and translate to a fail result so the response body explains which dependency is down.OK body or a specific status code range. If yours does, wrap or override the controller rather than fighting the JSON format.bin/console debug:container --tag=<bundle-check-tag> (the tag name is in the bundle’s services.yaml) to confirm which checks are actually registered — a silently unregistered custom check is the most common “why is my check not running?” issue.predis/predis ^2.2). If you were previously held back on an older Predis, run composer update macpaw/symfony-health-check-bundle predis/predis --with-dependencies and re-verify the Redis check's failure mode after the bump — a client major/minor change can alter exception types your try/catch relies on.macpaw_health_check) and the check class names are your integration surface. Pin the package version in composer.json (a caret on ^2.1 is fine given the 2.x history) and re-check the README on upgrade; there are no dependents listed, so you will not get community warning shots.src/ code (it is small enough to audit in an afternoon), fork it if you need guaranteed fixes, and keep your custom checks decoupled from its result classes so you can swap the transport later. v2.1.0 added a second maintainer to the commit log, which is mildly reassuring but does not change the risk profile.services.yaml.How can I help you explore Laravel packages today?