paulund

Health Checks

Health Checks

Health checks are lightweight, automated tests that verify whether your application and its dependencies are functioning correctly. They run on a regular interval -- typically every few seconds to every minute -- and return a simple status: healthy or unhealthy.

Why Health Checks Matter

Without health checks, you often do not know that something is broken until a user reports it. Health checks give you that awareness proactively. They are used by load balancers to route traffic away from failing instances, by orchestrators like Kubernetes to decide whether to restart a container, and by monitoring systems to trigger alerts before an outage becomes visible to users.

The Health Check Endpoint

Most applications expose health checks as a dedicated HTTP endpoint, commonly at /health or /healthz. A monitoring system or load balancer polls this endpoint at regular intervals. The endpoint returns:

  • An HTTP 200 status code if everything is healthy.
  • An HTTP 503 (or similar non-success code) if something is wrong.
  • A JSON body that describes the status of each individual check, making it straightforward to diagnose which component is failing.

A simple healthy response might look like this:

{
  "status": "healthy",
  "checks": {
    "database": "ok",
    "cache": "ok",
    "external_api": "ok"
  },
  "timestamp": "2026-02-04T12:00:00Z"
}

An unhealthy response might look like this:

{
  "status": "unhealthy",
  "checks": {
    "database": "ok",
    "cache": "timeout",
    "external_api": "ok"
  },
  "timestamp": "2026-02-04T12:00:00Z"
}

What to Check

A good health check covers every layer that your application depends on. The following are the most common targets:

Database Connectivity

Run a lightweight query against your database -- a simple SELECT 1 or equivalent -- to confirm the connection is alive. This catches connection pool exhaustion, network issues between your application and the database server, and database crashes.

Cache Availability

If your application relies on a cache layer (Redis, Memcached, or similar), verify that you can both write and read a test key. A failing cache may not bring your application down immediately, but it will degrade performance and can cause subtle bugs if left undetected.

External Dependencies

Many applications call third-party APIs -- payment gateways, email providers, external data sources. Your health check should verify that these services are reachable. A common approach is to make a lightweight request (such as a status or ping endpoint) to each external service and record the result. Be cautious here: you do not want a slow third-party service to make your own health check time out. Set a short timeout on each external check and treat a timeout as unhealthy rather than letting it block the entire response.

Disk Space

If your application writes to local storage -- log files, uploaded files, temporary data -- a full disk will cause failures that are difficult to diagnose. A simple check of available disk space is cheap to run and catches this class of problem early.

Application-Specific Checks

Beyond infrastructure, you may want to verify that core application logic is working. For example, you might check that a background job queue is being processed, that a scheduled task ran recently, or that a critical configuration value is present. These checks are specific to your application and are best added as you discover the failure modes that matter most to you.

Health Checks vs. Uptime Monitoring

Health checks and uptime monitoring are related but distinct. A health check runs from within your infrastructure and tests internal components. Uptime monitoring (such as Grafana Cloud Synthetic Monitoring) runs from external locations and tests whether your application is reachable and responding correctly from the outside. A robust observability setup uses both: health checks catch internal problems early, whilst uptime monitoring confirms that everything works end-to-end for your users.

Implementing Health Checks in Laravel

Laravel does not ship with a built-in health check endpoint, but adding one is straightforward. You can create a simple controller that runs each of your checks and returns the appropriate HTTP status and JSON body.

use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;

class HealthController
{
    public function __invoke(): JsonResponse
    {
        $checks = [];
        $healthy = true;

        // Database check
        try {
            DB::select('SELECT 1');
            $checks['database'] = 'ok';
        } catch (\Exception $e) {
            $checks['database'] = 'error';
            $healthy = false;
        }

        // Cache check
        try {
            Cache::put('_health_check', true, 10);
            $checks['cache'] = Cache::get('_health_check') ? 'ok' : 'error';
        } catch (\Exception $e) {
            $checks['cache'] = 'error';
            $healthy = false;
        }

        return new JsonResponse([
            'status' => $healthy ? 'healthy' : 'unhealthy',
            'checks' => $checks,
            'timestamp' => now()->toIso8601String(),
        ], $healthy ? 200 : 503);
    }
}

Register the route in your routes/web.php:

Route::get('/health', HealthController::class);

This gives you a single endpoint that a load balancer or monitoring tool can poll to determine whether the instance is ready to serve traffic.