ledgehq / ledge-laravel
Health, uptime, and compliance monitoring for your Laravel applications.
Requires
- php: ^8.3
- ext-json: *
- ext-sodium: *
- ext-zlib: *
- guzzlehttp/guzzle: ^7.8
- illuminate/bus: ^13.0
- illuminate/console: ^13.0
- illuminate/contracts: ^13.0
- illuminate/database: ^13.0
- illuminate/http: ^13.0
- illuminate/queue: ^13.0
- illuminate/support: ^13.0
- spatie/db-dumper: ^4.1
- symfony/process: ^7.0 || ^8.0
- vlucas/phpdotenv: ^5.6
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.18
- orchestra/testbench: ^11.0
- phpunit/phpunit: ^12.0
- spatie/laravel-health: ^1.40
Suggests
- spatie/laravel-health: Wrap its checks with LedgeHQ\Ledge\Checks\SpatieHealthCheck to expose them to Ledge
Provides
None
Conflicts
None
Replaces
None
README
Health, uptime, and compliance monitoring for Laravel applications. This package exposes a secured health endpoint that the Ledge service polls; it is the Laravel counterpart of the Craft CMS plugin and returns the same payload shape.
Requirements
- PHP 8.3+ with the
sodiumandzlibextensions - Laravel 13+
Installation
composer require ledgehq/ledge-laravel php artisan ledge:generate-key
The service provider is auto-discovered. ledge:generate-key writes LEDGE_SECRET_KEY to your .env; pass --show to print a key without writing it.
Configuration
Requests must carry the secret in the X-Ledge-Key header:
curl -H "X-Ledge-Key: your-secret-key" https://your-app.com/_ledge/health
The header is the only accepted method by default. Set LEDGE_ALLOW_QUERY_KEY=true to also accept ?key=; the header and parameter names are configurable with LEDGE_KEY_HEADER and LEDGE_QUERY_KEY_PARAM.
Every threshold, status level, path, and toggle has a LEDGE_* env var. Publish the annotated config to change defaults in code:
php artisan vendor:publish --tag=ledge-config
Thresholds come in _degraded_at / _unhealthy_at pairs, inclusive; set a tier to null (env: empty or off) to disable it. Condition checks (debugMode, missing env vars, unwritable storage, a stale scheduler) take a status level instead: healthy, degraded, or unhealthy. Invalid values are logged and replaced by the default. Disable checks with LEDGE_DISABLED_CHECKS=scheduler,failedLogins or the enabled_checks map.
Every check echoes the values it used under a thresholds key so the Ledge dashboard can show why a site tripped.
Checks
| Check | What it reports |
|---|---|
database |
Connectivity and response time on the default connection |
queue |
Failed jobs within LEDGE_QUEUE_FAILED_WINDOW; pending and stale counts (exact on the database driver, pending only elsewhere) |
disk |
Usage of the storage path, plus an optional absolute free-bytes floor |
memory |
PHP memory usage against memory_limit |
debugMode |
APP_DEBUG on |
environment |
Variables declared with a value in .env.example that are not defined; blank example keys are listed as optional and never trip |
storage |
Writability of storage/logs, storage/framework/*, and bootstrap/cache |
scheduler |
Time since schedule:run last completed; silent until the first run is seen |
failedLogins |
Auth Failed events within LEDGE_FAILED_LOGINS_WINDOW, counted in the cache |
cache |
Write/read/delete roundtrip on the default cache store |
redis |
PING on the default Redis connection; only reported when cache, queue, session, or broadcasting use Redis |
horizon |
Horizon master supervisors running / paused, plus supervisors, processes, per-queue wait times, and recently failed jobs; only reported when laravel/horizon is installed |
optimized |
Config, route, and event caches; trips only when APP_ENV=production |
phpVersion |
Support phase of the running PHP branch (active, security, eol) |
packages |
Installed Composer packages with a newer stable release on Packagist, plus frameworkVersion for laravel/framework |
The packages comparison is made by php artisan ledge:packages:refresh, scheduled daily by the package (turn that off with LEDGE_PACKAGES_AUTO_REFRESH=false and schedule it yourself). The health poll only reads the stored result and never calls Packagist.
Custom checks
Three ways to add your own, all registered after the defaults:
// config/ledge.php: classes resolved from the container 'checks' => [App\Ledge\SearchIndexCheck::class], // A service provider: a class or an inline closure use LedgeHQ\Ledge\Checks\CheckResult; use LedgeHQ\Ledge\Facades\Ledge; Ledge::register(new SearchIndexCheck()); // implements LedgeHQ\Ledge\Checks\CheckInterface Ledge::check('search', fn (string $name) => CheckResult::healthy($name, ['documents' => 12])); Ledge::forget('failedLogins'); // An existing spatie/laravel-health check (install that package yourself) use LedgeHQ\Ledge\Checks\SpatieHealthCheck; use Spatie\Health\Checks\Checks\UsedDiskSpaceCheck; Ledge::register(SpatieHealthCheck::wrap(UsedDiskSpaceCheck::new()));
A check returns a CheckResult (healthy, degraded, or unhealthy, with a data array and an optional output line), or null to leave itself out of the payload for that run. Spatie statuses map ok → healthy, warning → degraded, failed / crashed → unhealthy, skipped → omitted.
Dependencies
GET /_ledge/dependencies returns the installed Composer package set ({composer: [{name, version}], generatedAt, hash}) for security-advisory matching, read from Composer's runtime data rather than the lockfile. The health payload carries the same hash as dependenciesHash and a composerLockHash of the raw composer.lock. Turn the endpoint off with LEDGE_DEPENDENCIES_ENABLED=false.
Backups
With LEDGE_ACQUIRE_ENABLED=true, Ledge can command the app to dump its database, encrypt the dump to a one-time key, and upload it to Ledge's storage. Commands are Ed25519-signed by Ledge, verified against keys discovered from the Ledge service, replay-guarded, and restricted to allowlisted upload and callback hosts. The work runs as a queued job (LedgeHQ\Ledge\Jobs\BackupJob), so a queue worker must be running.
The dump uses mysqldump, mariadb-dump, pg_dump, or sqlite3 via spatie/db-dumper; point LEDGE_ACQUIRE_DUMP_BINARY_PATH at the directory holding the binary if it is not on the worker's PATH. To use a different dump pipeline, bind your own LedgeHQ\Ledge\Acquire\Database\DatabaseDumper.
See docs/acquire-protocol.md for the protocol, every config option, and the failure reasons.
Development
composer install
composer test
composer check-cs
composer phpstan