Robust application monitoring for Laravel web apps

Quality focus areas for a modern web application

Building a stable web app is key to delighting customers and delivering value. There are a few core pieces to ship a quality web app, outside of the core business logic:

  1. Write unit tests for your application
  2. Enable application error reporting
  3. Enable application monitoring to be notified when things go wrong
  4. Enable application tracing and logs to deep dive into where things are going slow
  5. ... and a whole lot more :)

For this post, we'll focus on application monitoring. By the end we will configure our Laravel app to use OhDear, an application monitoring service by the awesome folks at Spatie to monitor various aspects of our app and server:

  • Web server (external POV: no code changes)
    • Website uptime
    • SSL certs expiration
    • Average latency
    • ...
  • Laravel application (internal POV: minimal code changes required)
    • CPU load
    • Disk space
    • Schedule is running
    • Database & redis are connecting properly
    • App is optimized
    • ...

OhDear overview

Setting up laravel-health and

Install dependencies

Following the install instructions, install laravel-health package via composer:

1composer install laravel-health

OhDear configuration

Sign up on to get a free 14-day trial for application monitoring (I am not sponsored, just a happy customer!). It's crucial to getting insights into your application and your web server.

Once you have an account, follow the laravel-health instructions to configure the package to use OhDear.

You'll have to publish the config config/health.php and modify the oh_dear_endpoint in the config to be enabled:

1'oh_dear_endpoint' => [
2 'enabled' => true,
4 /*
5 * When this option is enabled, the checks will run before sending a response.
6 * Otherwise, we'll send the results from the last time the checks have run.
7 */
8 'always_send_fresh_results' => true,
10 /*
11 * The secret that is displayed at the Application Health settings at Oh Dear.
12 */
13 'secret' => env('OH_DEAR_HEALTH_CHECK_SECRET'),
15 /*
16 * The URL that should be configured in the Application health settings at Oh Dear.
17 */
18 'url' => '/oh-dear-health-check-results',
19 ],

Generate a random health check secret key and put in your .env.:


Enable OhDear Application health check for your domain

To enable Application health on your OhDear account: add your domain (if you haven't already) and then turn on Application health. Open up the Settings section and set:

  1. Health Report URL to: https://{my-domain}.com/oh-dear-health-check-results
  2. Health Report Secret to your .env secret for OH_DEAR_HEALTH_CHECK_SECRET
  3. Save changes settings

Including the most important health checks

Following instructions for registering health checks, create a new Service Provider: HealthCheckServiceProvider via php artisan make:provider HealthCheckServiceProvider and add health checks to the boot method:

1namespace App\Providers; ...
3use Illuminate\Support\ServiceProvider;
4use Spatie\CpuLoadHealthCheck\CpuLoadCheck;
5use Spatie\Health\Checks\Checks\CacheCheck;
6use Spatie\Health\Checks\Checks\DatabaseCheck;
7use Spatie\Health\Checks\Checks\DebugModeCheck;
8use Spatie\Health\Checks\Checks\EnvironmentCheck;
9use Spatie\Health\Checks\Checks\OptimizedAppCheck;
10use Spatie\Health\Checks\Checks\RedisCheck;
11use Spatie\Health\Checks\Checks\ScheduleCheck;
12use Spatie\Health\Checks\Checks\UsedDiskSpaceCheck;
13use Spatie\Health\Facades\Health;
15class HealthCheckServiceProvider extends ServiceProvider
17 public function register() ...
18 {
19 //
20 }
22 public function boot()
23 {
24 Health::checks([
25 UsedDiskSpaceCheck::new(),
26 DatabaseCheck::new(),
27 CpuLoadCheck::new()
28 ->failWhenLoadIsHigherInTheLast5Minutes(2.0)
29 ->failWhenLoadIsHigherInTheLast15Minutes(1.5),
30 DebugModeCheck::new(),
31 EnvironmentCheck::new(),
32 ScheduleCheck::new()->heartbeatMaxAgeInMinutes(5),
33 CacheCheck::new(),
34 OptimizedAppCheck::new(),
35 RedisCheck::new(),
36 ]);
37 }

Each health check is configurable, for example you can modify the CPU load alerting properties via:

  • failWhenLoadIsHigherInTheLast5Minutes
  • failWhenLoadIsHigherInTheLast15Minutes

The Schedule check has an important modification: heartbeatMaxAgeInMinutes which sets how long the schedule can not report to be running, before firing an alert. I've found this to produce a lot of noise with the default 1 minute heartbeat, so I'd recommend at least 2+ minutes, but I would find tune based on your deployment / application.

Eliminate false monitoring alarms during deployments

These health checks will be run every minute, when OhDear pings your health check URL. Sometimes it will ping your app when you are deploying, so these checks will fail, and that is to be expected, but we will fix it! So to fix these false alarms, OhDear provides a CLI tool that can allow you to put your site in a "maintenance window".

You will need to install the ohdear-cli tool on your server, then modify your deployment script to start a maintenance window on deploy begin, and end it on deploy finish:

1# OhDear ENV token
8# Start maintenance for ohdear checks (site-id, seconds)
17# Stop maintenance period
18$OH_DEAR_CLI maintenance-period:stop $OH_DEAR_SITE_ID

What monitoring we've achieved so far

OhDear brings a lot of website monitoring out of the box, when you onboard your domain:

  • Website uptime
  • SSL certs expiration
  • Domain expiration

When we add laravel-health into the mix and turn on Application health as a health check for our website in OhDear, we unlock a whole lot more monitoring:

  • Disk space
  • CPU load
  • Schedule
  • DB & Redis connections
  • App optimized
  • Environment + debug check

This list of monitoring capabilities alone is worth gold! We can enable Slack or other notifications via OhDear's settings, depending on what works for your team. This means you will get alerted when a health check fails, when the website is down, or when the CPU load is too high.

What's next?

With our schedule check, we are only checking if the schedule is running on an interval. We are not checking the individual entries we have in our Laravel Scheduler. There is yet another package for this: spatie/laravel-schedule-monitor that will enable this level of granularity, and I'd highly recommend it!