letflow / laravel-api-status
Provide status and health api endpoints
Requires
- php: ^8.3
- guzzlehttp/guzzle: ^7.0
- illuminate/config: ^12.0 || ^13.0
- illuminate/console: ^12.0 || ^13.0
- illuminate/support: ^12.0 || ^13.0
Requires (Dev)
- orchestra/testbench: ^10.0 || ^11.0
- phpunit/phpunit: ^12.0 || ^13.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-07 18:40:59 UTC
README
This package creates two routes /status and /health to expose API status and health checks.
Health check results
Each check reports a status, plus time as the measured duration in seconds.
Durations are measured with hrtime(), which is monotonic, so an NTP correction
during a check cannot distort them.
status | meaning | affects the HTTP status |
|---|---|---|
ok | the check passed | no |
failed | the check failed | yes, /health returns 500 |
snoozed | the check failed while silenced | no |
skipped | the check was not run, because it is silenced in skip mode | no |
A check that took longer than its timeout also carries slow: true. The flag
never changes status: a slow check that passed is still ok.
Silencing alarms during a deploy
When a planned change makes a check fail predictably — restarting the FTP server that an FTP check watches, for instance — silence it instead of getting paged:
# Silence every check for the default duration, running them but hiding failures
php artisan api-status:snooze
# Do not even run the ftp check for 15 minutes
php artisan api-status:snooze ftp --skip --minutes=15 --reason="ftp restart"
# Every check whose name starts with ftp, until a given time
php artisan api-status:snooze 'ftp*' --until="2026-09-07 18:00"
# See what would happen, without writing anything
php artisan api-status:snooze ftp --minutes=15 --dry-run
# The deploy is over: remove every silence
php artisan api-status:unsnooze
# Or just one, or a pattern
php artisan api-status:unsnooze ftp
php artisan api-status:unsnooze 'ftp*' --dry-run
Two modes:
snooze(the default) runs the check and reports its real result, but a failure comes back assnoozedinstead offailed, so/healthstays 200.--skipdoes not run the check at all. Use it when the resource is known to be down and waiting for its timeout is pointless.
While a silence is active, the check also reports snoozed_until, and
snooze_reason if one was given.
Silencing something already silenced overwrites it: the mode becomes the new one and the expiry is recomputed from now. The command prints what it replaced.
A silence lives in the cache and expires on its own. Notes on that:
- A per-check silence wins over the global one.
- If the cache is unavailable there is no silence, and checks fail exactly as they would otherwise. A check that verifies the cache therefore keeps failing when the cache is broken, which is the point.
- The commands, unlike the checks, fail loudly (exit code 1) when they cannot reach the cache, so a silence never looks set when it is not.
Configure the defaults in config/api-status.php under snooze: which cache
store holds the keys, their prefix, and the default duration in minutes.
Timeouts
Every check accepts timeout, in seconds, defaulting to api-status.timeout
(10). On its own it only drives the slow flag.
To make a driver actually honour a timeout, declare the overrides it needs under
connection_config. They are merged onto the underlying connection config, and
the check runs against an ephemeral connection built from the result:
'checks' => [
'db' => [
'driver' => 'db',
'connection' => 'mysql',
'timeout' => 3,
'connection_config' => [
'options' => [PDO::ATTR_TIMEOUT => 3],
],
],
'cache' => [
'driver' => 'cache',
'store' => 'redis',
'timeout' => 3,
'connection_config' => [
'read_timeout' => 3,
],
],
],
There is no uniform API to impose a timeout, because every driver names it
differently: phpredis uses timeout and read_timeout, predis timeout and
read_write_timeout, memcached Memcached::OPT_CONNECT_TIMEOUT, PDO
PDO::ATTR_TIMEOUT. Declaring them per check avoids a mapping table that would
guess at other libraries' internals and go stale.
connection_config is opt-in, and worth knowing what it costs:
- Without it, checks reuse the application's connection, as they always have.
- With it, each request to
/healthopens a new connection, with its TCP and authentication handshake. In exchange it exercises the connect path for real, which is what a connection timeout is about. httpdoes not use it: it already honourstimeoutdirectly.diskandstoragecannot honour a timeout at all.disk_free_space()andis_readable()on a hung NFS mount cannot be interrupted from PHP, so for those two the timeout is only ever informative.- On a queue check, the ephemeral connection stays memoised:
QueueManagerhas no method to forget it in Laravel 12 or 13. The name is fixed, so it is one retained object per process, worth knowing under Octane.
One caveat about the db check: Connection::getPdo() returns an already
resolved PDO without touching the network. If something opened the connection
earlier in the process, the check passes without proving anything. Setting
connection_config on it also fixes that, because the ephemeral connection is
forced to be new.