gavtaylor/laravel-health-route
A small Laravel package that adds a configurable health check route to your application, making it easy for uptime monitors, load balancers, and deployment checks to verify the app is running and responding.
Install the package and publish its config:
composer require gavtaylor/laravel-health-route
php artisan vendor:publish --tag=health-route-config
Open the published config file first — it is the single source of truth for the health route URI, the middleware guarding it, and the ordered list of checks that run. Everything else in this package is a consequence of that array.
Register the checks you care about and hit the route:
// config/health-route.php (shape may differ slightly — read the published file)
'checks' => [
\Gavtaylor\HealthRoute\Checks\StorageWritableCheck::class,
\Gavtaylor\HealthRoute\Checks\LogWritableCheck::class,
\Gavtaylor\HealthRoute\Checks\EnvironmentCheck::class,
],
curl -s -H 'Accept: application/json' https://app.test/health | jq
First use case: a post-deploy smoke test. v0.2.0's StorageWritableCheck and LogWritableCheck catch the two most common "it deployed but it's broken" failures — storage/ owned by the wrong user, and a log path that the web user can't write to. Run the endpoint immediately after php artisan migrate in your deploy script and fail the deploy on a non-2xx response.
Use JSON for machines, HTML for humans. Point uptime monitors, Kubernetes probes, and CI gates at the JSON representation — it is stable, keeps null for missing messages, and preserves your configured check order. Keep the HTML view for the "open it in a browser and eyeball it" case.
Group checks by intent rather than dumping everything in one list. A readiness endpoint (storage, log, environment) and a liveness endpoint (app boots at all) have different failure semantics; registering two route entries with different check subsets keeps your orchestrator from restarting pods over a full disk.
Custom checks follow the bundled ones. The three v0.2.0 checks are the reference implementations — read EnvironmentCheck first as the simplest example of the contract, then StorageWritableCheck for one that touches the filesystem. Write your own (queue connectivity, a required config key being present, an external API handshake) and register it in the same config array.
Gate deploys in CI:
STATUS=$(curl -s -o /tmp/health.json -w '%{http_code}' "$APP_URL/health")
jq . /tmp/health.json
[ "$STATUS" = "200" ] || exit 1
Protect the route deliberately. Health endpoints enumerate infrastructure. Apply auth, an IP allowlist, or a signed path segment via the config's middleware setting — and if EnvironmentCheck reports env/debug values, be sure it is never reachable from the public internet.
HTML and JSON disagree by design in v0.2.0. The HTML view sorts checks alphabetically and renders - for a missing message; JSON preserves configured order and emits null. Do not write assertions or snapshot tests that assume parity between the two — assert against JSON.
Alphabetical HTML ordering hides causality. If a check depends on a prior one (storage writable before something that writes), the HTML view will scramble the narrative. Use JSON when debugging an ordering-sensitive failure.
Run checks as the web user. A LogWritableCheck that passes under php artisan tinker can fail under www-data/php-fpm. Verify permissions as the actual serving user, and remember CLI-side storage:link and directory creation may have produced root-owned paths.
StorageWritableCheck only proves the disk(s) it is configured for. It will not tell you S3 credentials are wrong or that a remote disk is throttled — that needs a separate check.
LogWritableCheck can have side effects depending on how it probes the log target. Keep it out of extremely high-frequency liveness probes if it writes a line each time.
EnvironmentCheck risks leaking configuration. Confirm exactly what it reports before exposing the route, and consider excluding it from any publicly reachable instance.
php artisan config:cache after every change to the checks array — otherwise you will spend twenty minutes debugging a config edit that was never loaded.
Pin the version. This is a 0.x package; minor releases may move things. composer require gavtaylor/laravel-health-route:^0.2 and read the changelog on each bump rather than floating on *.
Debugging an opaque failure: request with Accept: application/json to get the raw per-check payload; the HTML view's - placeholder tells you a message is absent but not why, and the JSON null plus check name is usually the fastest path to the offending class.
How can I help you explore Laravel packages today?