Table Of Contents
- Introduction
- What Are Laravel Health Checks?
- Setting Up Laravel Health Checks
- Essential Health Check Types
- Advanced Health Check Implementations
- Monitoring and Alerting Integration
- Troubleshooting Common Issues
- Best Practices and Security
- Integration with CI/CD and DevOps
- FAQ
- Conclusion
Introduction
Every Laravel developer has experienced that sinking feeling when users report that "the website is down" while your server monitoring shows everything is green. The truth is, a running server doesn't guarantee a healthy application. Your database might be unreachable, your cache could be failing, or your queue workers might have stopped processing jobs—all while your web server continues to serve error pages.
Laravel health checks solve this critical gap by providing automated ways to monitor your application's internal state and dependencies. Rather than waiting for users to report issues, health checks proactively monitor essential services and alert you to problems before they impact your users.
In this comprehensive guide, you'll learn how to implement robust health checks in your Laravel application, set up automated monitoring, configure alerting systems, and establish best practices that will keep your application running smoothly. Whether you're managing a small startup application or an enterprise-level system, these techniques will help you maintain higher uptime and catch issues early.
What Are Laravel Health Checks?
Laravel health checks are automated tests that verify whether your application and its dependencies are functioning correctly. Unlike traditional server monitoring that only checks if your web server is responding, health checks examine the actual state of your application's critical components.
Core Components Monitored
A comprehensive health check system typically monitors:
- Database connectivity and query performance
- Cache systems (Redis, Memcached) availability
- Queue workers and job processing status
- File system permissions and storage availability
- External APIs and third-party service connections
- Application-specific business logic requirements
Benefits of Implementation
Implementing health checks provides several key advantages:
- Proactive issue detection before users are affected
- Faster troubleshooting with specific component status
- Improved uptime through early warning systems
- Better incident response with detailed health reports
- Enhanced monitoring integration with tools like Pingdom or New Relic
Setting Up Laravel Health Checks
The most popular and robust solution for Laravel health checks is the spatie/laravel-health package, which provides a comprehensive framework for monitoring your application state.
Installing the Health Package
Begin by installing the package via Composer:
composer require spatie/laravel-health
After installation, publish and run the migrations:
php artisan vendor:publish --tag="health-migrations"
php artisan migrate
Publish the configuration file to customize your health checks:
php artisan vendor:publish --tag="health-config"
Basic Configuration
The health check configuration is stored in config/health.php
. Here's a basic setup:
<?php
return [
'oh_dear_endpoint' => [
'enabled' => env('OH_DEAR_HEALTH_CHECK_ENABLED', true),
'secret' => env('OH_DEAR_HEALTH_CHECK_SECRET'),
'url' => '/health',
],
'checks' => [
Spatie\Health\Checks\Checks\OptimizedAppCheck::class,
Spatie\Health\Checks\Checks\DebugModeCheck::class,
Spatie\Health\Checks\Checks\EnvironmentCheck::class,
Spatie\Health\Checks\Checks\DatabaseCheck::class,
Spatie\Health\Checks\Checks\CacheCheck::class,
Spatie\Health\Checks\Checks\QueueCheck::class,
Spatie\Health\Checks\Checks\ScheduleCheck::class,
],
];
Creating Your First Health Check Endpoint
Add the health check route to your routes file:
// routes/web.php
Route::get('/health', [\Spatie\Health\Http\Controllers\HealthCheckController::class, 'check']);
Now you can access your health check endpoint at https://yourapp.com/health
and receive a JSON response showing the status of all configured checks.
Essential Health Check Types
Database Health Checks
Database connectivity is typically the most critical component to monitor. The package includes several database-related checks:
// Custom database performance check
use Spatie\Health\Checks\Check;
use Spatie\Health\Checks\Result;
class DatabasePerformanceCheck extends Check
{
public function run(): Result
{
$start = microtime(true);
try {
DB::select('SELECT 1');
$duration = (microtime(true) - $start) * 1000;
if ($duration > 1000) { // More than 1 second
return Result::make()->failed("Database query took {$duration}ms");
}
return Result::make()->ok();
} catch (Exception $e) {
return Result::make()->failed("Database connection failed: {$e->getMessage()}");
}
}
}
Cache System Monitoring
Cache failures can severely impact application performance. Monitor your cache systems with dedicated checks:
use Spatie\Health\Checks\Check;
use Spatie\Health\Checks\Result;
use Illuminate\Support\Facades\Cache;
class CachePerformanceCheck extends Check
{
public function run(): Result
{
$testKey = 'health_check_' . time();
$testValue = 'test_data';
try {
// Test write operation
Cache::put($testKey, $testValue, 60);
// Test read operation
$retrieved = Cache::get($testKey);
// Cleanup
Cache::forget($testKey);
if ($retrieved !== $testValue) {
return Result::make()->failed('Cache read/write mismatch');
}
return Result::make()->ok();
} catch (Exception $e) {
return Result::make()->failed("Cache error: {$e->getMessage()}");
}
}
}
Queue Worker Health Checks
Queue workers are essential for background job processing. Monitor their status to ensure jobs are being processed:
use Spatie\Health\Checks\Check;
use Spatie\Health\Checks\Result;
use Illuminate\Support\Facades\DB;
class QueueWorkerCheck extends Check
{
public function run(): Result
{
// Check for failed jobs
$failedJobs = DB::table('failed_jobs')->count();
if ($failedJobs > 10) {
return Result::make()->failed("Too many failed jobs: {$failedJobs}");
}
// Check for old pending jobs
$oldJobs = DB::table('jobs')
->where('created_at', '<', now()->subMinutes(30))
->count();
if ($oldJobs > 0) {
return Result::make()->failed("Jobs not processing: {$oldJobs} jobs older than 30 minutes");
}
return Result::make()->ok();
}
}
Advanced Health Check Implementations
Custom Application-Specific Checks
Beyond infrastructure monitoring, create checks for your specific business logic:
use Spatie\Health\Checks\Check;
use Spatie\Health\Checks\Result;
class PaymentGatewayCheck extends Check
{
public function run(): Result
{
try {
// Test payment gateway connectivity
$response = Http::timeout(10)->get('https://api.paymentgateway.com/health');
if ($response->successful()) {
return Result::make()->ok('Payment gateway responsive');
}
return Result::make()->failed('Payment gateway unreachable');
} catch (Exception $e) {
return Result::make()->failed("Payment gateway error: {$e->getMessage()}");
}
}
}
File System and Storage Checks
Monitor your application's ability to read and write files:
class StorageHealthCheck extends Check
{
public function run(): Result
{
$testFile = 'health_check_' . time() . '.txt';
$testContent = 'Health check test';
try {
// Test write
Storage::disk('local')->put($testFile, $testContent);
// Test read
$content = Storage::disk('local')->get($testFile);
// Cleanup
Storage::disk('local')->delete($testFile);
if ($content !== $testContent) {
return Result::make()->failed('File system read/write mismatch');
}
// Check available space
$freeSpace = disk_free_space(storage_path());
$freeSpaceGB = $freeSpace / (1024 * 1024 * 1024);
if ($freeSpaceGB < 1) {
return Result::make()->failed("Low disk space: {$freeSpaceGB}GB remaining");
}
return Result::make()->ok();
} catch (Exception $e) {
return Result::make()->failed("Storage error: {$e->getMessage()}");
}
}
}
Performance-Based Health Checks
Monitor application performance metrics:
class PerformanceHealthCheck extends Check
{
public function run(): Result
{
$start = microtime(true);
// Simulate typical application load
User::take(10)->get();
$orders = Order::with('items')->take(5)->get();
$duration = (microtime(true) - $start) * 1000;
if ($duration > 2000) { // More than 2 seconds
return Result::make()->failed("Application response slow: {$duration}ms");
}
if ($duration > 1000) { // Warning threshold
return Result::make()->warning("Application response degraded: {$duration}ms");
}
return Result::make()->ok("Response time: {$duration}ms");
}
}
Monitoring and Alerting Integration
Setting Up Automated Monitoring
Configure automated monitoring by running health checks on a schedule. Add this to your app/Console/Kernel.php
:
protected function schedule(Schedule $schedule)
{
$schedule->command('model:prune', ['--model' => [HealthCheckResultHistory::class]])
->daily();
$schedule->call(function () {
Artisan::call('health:check');
})->everyMinute();
}
Integration with External Services
Oh Dear Integration
Oh Dear provides seamless integration with Laravel health checks:
// In your .env file
OH_DEAR_HEALTH_CHECK_ENABLED=true
OH_DEAR_HEALTH_CHECK_SECRET=your-secret-key
// The package automatically handles Oh Dear integration
Custom Webhook Notifications
Create custom webhooks for your monitoring tools:
use Spatie\Health\Facades\Health;
class HealthCheckNotification
{
public function handle()
{
$results = Health::check();
if ($results->containsFailingCheck()) {
$this->sendSlackNotification($results);
$this->sendEmailAlert($results);
}
}
private function sendSlackNotification($results)
{
$failedChecks = $results->failingChecks();
$message = "🚨 Health Check Alert\n";
foreach ($failedChecks as $check) {
$message .= "• {$check->label}: {$check->notificationMessage}\n";
}
Http::post(config('services.slack.webhook'), [
'text' => $message
]);
}
}
Dashboard and Reporting
Create a simple health dashboard for your team:
// routes/web.php
Route::get('/admin/health', function () {
$results = Health::check();
return view('admin.health', compact('results'));
});
{{-- resources/views/admin/health.blade.php --}}
@extends('layouts.app')
@section('content')
<div class="container">
<h1>Application Health Dashboard</h1>
<div class="row">
@foreach($results->storedCheckResults as $result)
<div class="col-md-4 mb-3">
<div class="card @if($result->status === 'failed') border-danger @elseif($result->status === 'warning') border-warning @else border-success @endif">
<div class="card-body">
<h5 class="card-title">{{ $result->check_name }}</h5>
<p class="card-text">
<span class="badge @if($result->status === 'failed') bg-danger @elseif($result->status === 'warning') bg-warning @else bg-success @endif">
{{ ucfirst($result->status) }}
</span>
</p>
@if($result->notification_message)
<p class="text-muted">{{ $result->notification_message }}</p>
@endif
<small class="text-muted">Last checked: {{ $result->updated_at->diffForHumans() }}</small>
</div>
</div>
</div>
@endforeach
</div>
</div>
@endsection
Troubleshooting Common Issues
Health Check Performance Problems
If health checks are taking too long, consider these optimizations:
1. Implement Timeouts
class OptimizedDatabaseCheck extends Check
{
public function run(): Result
{
try {
DB::select(DB::raw('SELECT 1'), [], 5); // 5-second timeout
return Result::make()->ok();
} catch (QueryException $e) {
if (str_contains($e->getMessage(), 'timeout')) {
return Result::make()->failed('Database timeout');
}
return Result::make()->failed("Database error: {$e->getMessage()}");
}
}
}
2. Use Connection Pooling
// config/database.php
'connections' => [
'health_check' => [
'driver' => 'mysql',
'host' => env('DB_HOST', '127.0.0.1'),
'database' => env('DB_DATABASE', 'forge'),
// Dedicated connection for health checks
'options' => [
PDO::ATTR_TIMEOUT => 5,
],
],
],
False Positive Alerts
Reduce false positives with smart retry logic:
class ReliableCheck extends Check
{
private $retries = 3;
public function run(): Result
{
$lastException = null;
for ($i = 0; $i < $this->retries; $i++) {
try {
// Your check logic here
return Result::make()->ok();
} catch (Exception $e) {
$lastException = $e;
if ($i < $this->retries - 1) {
sleep(1); // Wait 1 second before retry
}
}
}
return Result::make()->failed("Failed after {$this->retries} attempts: {$lastException->getMessage()}");
}
}
Memory and Resource Management
Prevent health checks from consuming too many resources:
class ResourceAwareCheck extends Check
{
public function run(): Result
{
$memoryLimit = ini_get('memory_limit');
$currentMemory = memory_get_usage(true);
// Don't run if memory usage is too high
if ($currentMemory > (0.8 * $this->parseMemoryLimit($memoryLimit))) {
return Result::make()->skipped('Skipped due to high memory usage');
}
// Your check logic here
return Result::make()->ok();
}
private function parseMemoryLimit($limit)
{
$unit = strtolower(substr($limit, -1));
$value = (int) substr($limit, 0, -1);
switch ($unit) {
case 'g': return $value * 1024 * 1024 * 1024;
case 'm': return $value * 1024 * 1024;
case 'k': return $value * 1024;
default: return $value;
}
}
}
Best Practices and Security
Security Considerations
1. Protect Health Check Endpoints
// routes/web.php
Route::middleware(['auth', 'can:view-health-checks'])->group(function () {
Route::get('/admin/health', [HealthController::class, 'dashboard']);
});
// For external monitoring, use secret tokens
Route::get('/health', function (Request $request) {
if ($request->get('token') !== config('app.health_check_token')) {
abort(403);
}
return app(HealthCheckController::class)->check();
});
2. Limit Information Exposure
class SecureHealthCheck extends Check
{
public function run(): Result
{
try {
// Perform your check
return Result::make()->ok('Service operational');
} catch (Exception $e) {
// Don't expose sensitive error details
Log::error('Health check failed: ' . $e->getMessage());
return Result::make()->failed('Service unavailable');
}
}
}
Performance Optimization
1. Cache Health Check Results
use Illuminate\Support\Facades\Cache;
class CachedHealthCheck extends Check
{
private $cacheKey = 'health_check_result';
private $cacheTTL = 60; // seconds
public function run(): Result
{
return Cache::remember($this->cacheKey, $this->cacheTTL, function () {
return $this->performActualCheck();
});
}
private function performActualCheck(): Result
{
// Your actual health check logic
return Result::make()->ok();
}
}
2. Implement Circuit Breaker Pattern
class CircuitBreakerCheck extends Check
{
private $failureThreshold = 5;
private $recoveryTime = 300; // 5 minutes
public function run(): Result
{
$failureCount = Cache::get('health_check_failures', 0);
$lastFailure = Cache::get('health_check_last_failure');
// Circuit is open
if ($failureCount >= $this->failureThreshold) {
if ($lastFailure && (time() - $lastFailure) < $this->recoveryTime) {
return Result::make()->skipped('Circuit breaker open');
}
// Reset circuit breaker
Cache::forget('health_check_failures');
Cache::forget('health_check_last_failure');
}
try {
// Perform check
$result = $this->performCheck();
if ($result->failed()) {
Cache::increment('health_check_failures');
Cache::put('health_check_last_failure', time());
} else {
Cache::forget('health_check_failures');
}
return $result;
} catch (Exception $e) {
Cache::increment('health_check_failures');
Cache::put('health_check_last_failure', time());
return Result::make()->failed($e->getMessage());
}
}
}
Integration with CI/CD and DevOps
Docker Health Checks
Integrate Laravel health checks with Docker health checks:
# Dockerfile
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost/health || exit 1
Kubernetes Probes
Configure Kubernetes to use your Laravel health checks:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: laravel-app
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/ready
port: 80
initialDelaySeconds: 5
periodSeconds: 5
CI/CD Pipeline Integration
Include health checks in your deployment pipeline:
# .github/workflows/deploy.yml
- name: Wait for deployment health check
run: |
timeout 300 bash -c 'until curl -f ${{ secrets.APP_URL }}/health; do sleep 10; done'
- name: Verify all health checks pass
run: |
response=$(curl -s ${{ secrets.APP_URL }}/health)
if echo "$response" | grep -q '"failed"'; then
echo "Health checks failed!"
exit 1
fi
FAQ
Q: How often should I run health checks? A: For critical systems, run basic health checks every 1-2 minutes. More resource-intensive checks can run every 5-10 minutes. Adjust frequency based on your application's criticality and the check's resource requirements.
Q: What's the difference between health checks and application monitoring? A: Health checks verify that your application components are functioning correctly, while monitoring tracks performance metrics over time. Health checks are binary (working/not working), whereas monitoring provides trends and analytics.
Q: Should health checks access production databases?
A: Yes, but use lightweight queries and consider read replicas for database checks. Simple queries like SELECT 1
are ideal as they test connectivity without impacting performance.
Q: How do I handle health check failures during deployments? A: Implement a grace period during deployments and use separate readiness and liveness checks. Configure your load balancer to remove instances that fail readiness checks while allowing time for deployment completion.
Q: Can health checks impact application performance? A: When implemented properly with caching, timeouts, and resource limits, health checks should have minimal impact. Avoid running expensive operations and use dedicated database connections for health checks.
Q: What should I do if external service health checks fail? A: Implement graceful degradation in your application. Log the failures, alert your team, but don't let external service failures bring down your entire application. Consider circuit breaker patterns for resilience.
Conclusion
Laravel health checks are essential for maintaining robust, reliable applications in production environments. By implementing comprehensive monitoring of your database, cache, queues, and application-specific components, you can detect issues before they impact your users and respond quickly when problems arise.
The key takeaways from this guide include: establishing both infrastructure and application-level health checks, implementing proper security and performance optimization, integrating with your existing DevOps workflows, configuring appropriate alerting and notification systems, and following best practices for resource management and error handling.
Remember that health checks are just one part of a comprehensive monitoring strategy. Combine them with application performance monitoring, log aggregation, and business metrics tracking for complete visibility into your application's health.
Ready to implement health checks in your Laravel application? Start with the basic database and cache checks outlined in this guide, then gradually add more sophisticated monitoring as your needs grow. Share your experience with Laravel health checks in the comments below—what challenges have you faced, and what creative solutions have you implemented?
Add Comment
No comments yet. Be the first to comment!