Looking to hire Laravel developers? Try LaraJobs

laravel-health-ui maintained by webrek

Description
A production health dashboard for Laravel: pluggable checks, a JSON endpoint for uptime monitors and a status page.
Last update
2026/06/08 04:56 (dev-main)
License
Downloads
0

Comments
comments powered by Disqus

Laravel Health UI

Latest Version on Packagist Total Downloads Tests PHP Version License

A production health dashboard for Laravel. Pluggable checks (database, cache, disk, external services), a JSON endpoint your uptime monitor can poll, and a clean status page — all from one route.

Quickstart

composer require webrek/laravel-health-ui

Visit /health for the status page, or poll it as JSON:

curl -H "Accept: application/json" https://your-app.test/health
{
  "status": "ok",
  "healthy": true,
  "checks": [
    {"name": "database", "status": "ok", "message": "Connection [sqlite] is reachable.", "meta": [], "duration_ms": 0.4},
    {"name": "cache", "status": "ok", "message": "Cache read/write succeeded.", "meta": [], "duration_ms": 0.2},
    {"name": "disk_space", "status": "ok", "message": "Disk is 41% full.", "meta": {"used_percent": 41}, "duration_ms": 0.1}
  ]
}

The endpoint returns 200 while the app is healthy and 503 the moment a check hard-fails — exactly what an uptime monitor (Pingdom, UptimeRobot, a Kubernetes liveness probe) needs.

What you get over Laravel's /up

Laravel's built-in /up route answers a single question: did the framework boot? That tells you nothing about whether your database is reachable, your cache is responding, the disk is filling up, or a third-party API you depend on is down. This package runs real checks, aggregates them, and shows you which one is unhealthy — on a status page and as machine-readable JSON.

Status model

Each check returns one of three statuses, and the overall status is the worst of them:

Status Meaning HTTP
ok (Operational) Healthy 200
warning (Degraded) Working but needs attention (e.g. disk 85% full, debug mode on) 200
failed (Down) A hard failure 503

Warnings keep the endpoint at 200 so you are not paged for degraded-but- serving conditions — only hard failures flip it to 503.

Built-in checks

Enable and tune them in config/health-ui.php:

'checks' => [
    'database'   => ['enabled' => true, 'connection' => null],
    'cache'      => ['enabled' => true, 'store' => null],
    'disk_space' => ['enabled' => true, 'path' => null, 'warning_threshold' => 80, 'failure_threshold' => 90],
    'debug_mode' => ['enabled' => true],
    'http'       => ['enabled' => true, 'endpoints' => [
        ['name' => 'Payments API', 'url' => 'https://api.example.com/health', 'timeout' => 5],
    ]],
],
  • database — runs select 1 on the connection.
  • cache — writes and reads back a value.
  • disk_space — warns/fails past the configured used-percentage thresholds.
  • debug_mode — warns when APP_DEBUG is on (a frequent production slip).
  • http — pings each external dependency and expects a 2xx.
  • queue_failed_jobs — warns/fails as the failed-jobs backlog grows.
  • schedule — fails when the scheduler heartbeat goes stale (see below).
  • migrations — warns when migrations are committed but not run here.
  • certificates — warns before a host's TLS certificate expires.

Scheduler heartbeat

The schedule check reads a timestamp your scheduler keeps fresh. Add a frequent task that stamps it:

// routes/console.php (or your schedule definition)
Schedule::call(fn () => cache()->forever('health-ui:schedule-heartbeat', now()->timestamp))
    ->everyMinute();

The check then fails if that heartbeat is older than max_age_minutes.

Writing your own check

Implement the Check contract. Return a Result, or just throw — the checker turns an exception into a failed result automatically.

use Webrek\HealthUi\Contracts\Check;
use Webrek\HealthUi\Result;

class RedisQueueDepthCheck implements Check
{
    public function name(): string
    {
        return 'queue_depth';
    }

    public function run(): Result
    {
        $depth = Redis::llen('queues:default');

        return $depth < 1000
            ? Result::ok($this->name(), "Queue depth is {$depth}.", ['depth' => $depth])
            : Result::warning($this->name(), "Queue is backing up ({$depth}).", ['depth' => $depth]);
    }
}

Register it (e.g. in a service provider):

app(\Webrek\HealthUi\HealthChecker::class)->register(new RedisQueueDepthCheck);

Command line

Run the checks from the CLI or a cron — it exits non-zero when unhealthy, so it plugs straight into deploy gates and alerting:

php artisan health:check

Configuration highlights

'route' => env('HEALTH_UI_ROUTE', 'health'),

// Protect the endpoint — it can expose internal details.
'middleware' => [],

// Cache the report so frequent polls don't run every check each hit.
'cache' => ['ttl' => 0, 'store' => null, 'key' => 'health-ui.report'],

Publish the config and views to customise them:

php artisan vendor:publish --tag=health-ui-config
php artisan vendor:publish --tag=health-ui-views

The endpoint can reveal internal state. In production, put it behind middleware (a token, a signed URL, or an internal-network restriction).

Requirements

Component Version
PHP 8.2+
Laravel 12.x

Testing

composer install
composer test

Contributing

See CONTRIBUTING.md.

Security

Please review the security policy before reporting a vulnerability.

License

The MIT License (MIT). See LICENSE.