Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Health Checker Laravel Package

arquivei/laravel-health-checker

A Laravel package for adding health check endpoints to your application. Configure checks for services like database, cache, and storage, then expose them via routes that return clear status responses—useful for monitoring, uptime probes, and load balancer checks.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish

composer require arquivei/laravel-health-checker
php artisan vendor:publish --tag=health-checker-config

Auto-discovery registers the service provider; you should not need to touch config/app.php. Confirm in bootstrap/providers.php (Laravel 11+) that nothing manual is required.

Look here first

  1. config/health-checker.php (or similarly named — check the publish tag output) — this is the whole integration surface: which checks run, the route path, the middleware stack, and the HTTP status mapping.
  2. src/Contracts / src/Checks in the vendor tree — the built-in database/cache/queue/storage checks are the best template for a custom one. Read one before writing your own; it tells you exactly what a check returns and whether failures are exceptions or result objects.
  3. The controller — you need to know whether it returns 200/503 or something custom, because that is what your load balancer and Kubernetes probes will key off.

First use case

Get a probe endpoint live in front of a load balancer before you write a single custom check:

php artisan serve &
curl -i http://localhost:8000/health

Expect a JSON body enumerating each dependency with a pass/fail and an overall status. Wire that URL into your ALB target group / k8s readinessProbe and you have the 80% case done. Everything below is refinement.

Implementation Patterns

Split liveness from readiness. This is the highest-value pattern with this package. A liveness probe should answer "is this process wedged?" and touch nothing external. A readiness probe should answer "should traffic go here?" and check the database, cache, and queue. If you point a liveness probe at the full check set, a five-second database blip restarts every container in your fleet simultaneously. Configure two routes (or two check groups) and register them as different probes.

Custom checks for your actual dependencies. The built-ins cover the framework defaults, not your application. The day-one additions are usually: an external API your request path depends on, a Redis instance that isn't the default cache store, and a critical downstream service. Model them on a built-in check:

final class BillingApiCheck implements Check
{
    public function __construct(private BillingClient $client) {}

    public function check(): Result
    {
        return $this->client->ping(timeout: 1)
            ? Result::ok('billing')
            : Result::fail('billing', 'unreachable');
    }
}

Register in the config array — but resolve it through the container, not new, so you can inject clients and swap fakes in tests.

Separate critical from non-critical. A dead queue worker means delayed jobs, not a broken site. A dead primary database means take the instance out of rotation. Encode that in the response: an overall degraded status with an HTTP 200 for the soft failures, and 503 only when a hard dependency is down. Otherwise you will page yourself at 3am because the S3 bucket used for nightly exports had a permissions change.

Cache the expensive checks. A load balancer probes every target every 5–30 seconds per node. A queue check that opens a connection, and an external API check that does a real round trip, at that frequency across 20 pods is meaningful load — and it can trip rate limits on the downstream service, turning your monitoring into an outage source. Wrap heavy checks in Cache::remember($key, 15, ...) and accept slightly stale results.

Protect the route, but don't authenticate it the way you authenticate users.

// config
'route' => [
    'path' => '/health',
    'middleware' => ['throttle:60,1'], // plus a token or IP allowlist
],

Session middleware on a probe route writes a session row on every probe from every node. throttle is safe; web and auth are not. If the output names hosts, buckets, or drivers, put it behind an IP allowlist or a static bearer token, or run a public minimal route and an internal detailed one.

Test it in the suite and in CI. Unit-test the controller with dependencies stubbed (Cache::shouldReceive, Http::fake), and add a post-deploy smoke step that curls the endpoint and fails the pipeline on a non-200. That catches the classic "config cached from the previous deploy, pointing at the old Redis host" failure immediately rather than on the next probe cycle.

Emit the result somewhere durable. A JSON body nobody reads is not monitoring. Log failures at warning, and push the overall status into your metrics pipeline (a StatsD gauge or a custom log-based metric) so you get history and alerting rather than only point-in-time probe state.

Gotchas and Tips

No global timeout is the failure mode that takes down a fleet. A synchronous check against a hung dependency blocks the PHP worker until max_execution_time kills it. Every probe times out, the orchestrator marks every instance unhealthy, and the whole service goes dark — because one dependency was slow. Give the endpoint a tight max_execution_time (2–3s), give each check its own client-side timeout (Http::timeout(1)), and make sure a slow check counts as a failure of that check, not a hang of the request.

Throwing checks return 500, not 503. If your custom check raises an exception instead of returning a failure result, the response is a generic error and your monitor will see a different signal class than intended. Catch inside the check and convert. Verify by deliberately breaking a dependency in staging — do not assume the package does this for you.

Checks with side effects. A queue check that pushes a real job to prove the queue works pollutes the queue and fails whenever no worker is running (which is a legitimate state during deploys). Prefer a connection-level check, or push to a dedicated low-priority queue with a discard handler. Some packages ship a queue check that does exactly this — read the source before enabling it against a production queue.

Filesystem checks and container filesystems. A write test to storage/ fails on a read-only root filesystem, and an S3 check can return false negatives when your IAM policy allows GetObject but not HeadBucket/ListBucket. Test both in the real runtime (container, not local), and drop a check you cannot make meaningful rather than carrying a permanently-red one — persistent false alarms train people to ignore the endpoint.

Driver mismatch. The cache check validates the default store. If your app reads from a different store (or a specific Redis connection) than the one the check inspects, you get a green probe over a broken cache. Check what the check actually resolves against, and configure it explicitly if the package allows.

Config and route caching. After changing the route path or check list, php artisan config:cache and route:cache must be re-run in the deploy. A stale cached config silently serves the old endpoint — a very common "the health check is fine but it's checking the wrong thing" bug.

Version pinning. The 4.x line has shipped with sparse release notes. Pin an exact version in composer.json, and when you bump, treat it as a diff-reading exercise rather than trusting the changelog — check the check-result contract and config key names specifically, since those are the two things a major bump is most likely to break. Budget an hour, not a minute.

Extension points worth knowing. Most packages in this space let you swap the controller (Route::get() your own, delegating to the checker), override the response shape, or hook before/after the check run. If the built-in JSON structure doesn't match what your platform expects (some orchestrators want {"status":"UP"}, some want a flat map of booleans), overriding the response formatter is cheaper than translating in a sidecar — look for a published view or a configurable formatter before you write a proxy.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log