How to Monitor Your PHP/Laravel App Uptime (and Get Notified When It Goes Down)
Your Laravel app went down at 2 AM. You found out at 9 AM when a customer emailed you. Sound familiar?
Most Laravel tutorials end at deployment. This one picks up there. By the end you'll have HTTP uptime monitoring, heartbeat checks for your queued jobs and Artisan commands, Slack alerts, a public status page, and a live status badge for your README — all in under 30 minutes, free.
The two failures PHP devs miss most
Laravel production failures fall into two patterns that are easy to detect but go unnoticed without the right tooling:
Endpoint outages — your app starts returning 500s or timing out. Users hit a broken page. You don't find out until they complain (or they don't complain and just leave).
Silent queue failures — your queue workers stop processing jobs. Emails stop sending. Webhooks stop being delivered. Reports stop generating. No exception is thrown. Nothing is logged. You just quietly stop getting results until something downstream breaks.
Both are solvable with a health endpoint and a monitoring tool. Let's wire them up.
Step 1: Add a health check route
Laravel doesn't ship with a health endpoint out of the box, but adding one takes about two minutes.
Install the spatie/laravel-health package:
composer require spatie/laravel-health
Publish and run the migrations:
php artisan vendor:publish --tag="health-migrations"
php artisan migrate
Register your checks in AppServiceProvider:
// app/Providers/AppServiceProvider.php
use Spatie\Health\Facades\Health;
use Spatie\Health\Checks\Checks\DatabaseCheck;
use Spatie\Health\Checks\Checks\CacheCheck;
use Spatie\Health\Checks\Checks\UsedDiskSpaceCheck;
public function boot(): void
{
Health::checks([
DatabaseCheck::new(),
CacheCheck::new(),
UsedDiskSpaceCheck::new()->failWhenUsedSpaceIsAbovePercentage(90),
]);
}
The package automatically registers a /up route. You can verify it locally:
php artisan serve
curl http://localhost:8000/up
# {"finishedAt":"...","checkResults":[...]}
A 200 means healthy. A 500 means something is wrong — and it tells you exactly what.
If you prefer a minimal, zero-dependency health check, you can wire one up yourself:
// routes/web.php
Route::get('/health', function () {
try {
DB::connection()->getPdo();
return response()->json(['status' => 'ok']);
} catch (\Exception $e) {
return response()->json(['status' => 'error', 'message' => $e->getMessage()], 500);
}
});
Either approach works. The important thing is that you have a URL that returns a non-200 when your app is in trouble.
Step 2: Set up HTTP uptime monitoring
With your health endpoint live, point Vigilmon at it:
- Sign up for a free account at vigilmon.online
- Click New Monitor → HTTP
- Enter
https://yourdomain.com/up(or/health) - Set check interval to 5 minutes (free tier)
- Save
Vigilmon will ping your endpoint every 5 minutes from multiple locations. If it returns a non-200 or times out, you'll get alerted immediately.
You can also add separate monitors for critical routes:
-
https://yourdomain.com/— your homepage is serving -
https://yourdomain.com/api/ping— your API layer is responding -
https://yourdomain.com/login— your auth flow is reachable
Each monitor runs independently. If your API goes down but the homepage is fine, you'll know exactly which layer is broken.
Step 3: Heartbeat monitoring for queued jobs
HTTP monitoring catches server outages. But what about jobs that silently stop running?
The heartbeat pattern: your job pings a URL at the end of every successful run. If Vigilmon stops receiving pings within the expected interval, it alerts you — whether the job failed, threw an exception, or stopped being dispatched altogether.
Here's a Laravel job with a heartbeat:
// app/Jobs/SendDailyDigestJob.php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Http;
class SendDailyDigestJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable;
public function handle(): void
{
// Your actual job logic
$this->sendDigestEmails();
// Ping the heartbeat URL on success
$heartbeatUrl = config('services.vigilmon.digest_heartbeat_url');
if ($heartbeatUrl) {
Http::timeout(5)->get($heartbeatUrl);
}
}
private function sendDigestEmails(): void
{
// your logic here
}
}
Add the URL to your config:
// config/services.php
'vigilmon' => [
'digest_heartbeat_url' => env('VIGILMON_DIGEST_HEARTBEAT_URL'),
],
# .env
VIGILMON_DIGEST_HEARTBEAT_URL=https://vigilmon.online/heartbeats/your-unique-token
In Vigilmon, create a Heartbeat Monitor:
- Click New Monitor → Heartbeat
- Set the expected interval (e.g. every 24 hours)
- Copy the unique ping URL
- Set it as
VIGILMON_DIGEST_HEARTBEAT_URLin your environment
Works with Artisan commands too
If you're running scheduled tasks via artisan schedule:run, the same pattern applies:
// app/Console/Commands/GenerateReportsCommand.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
class GenerateReportsCommand extends Command
{
protected $signature = 'reports:generate';
protected $description = 'Generate daily reports';
public function handle(): int
{
$this->generateReports();
// Ping heartbeat on success
$url = config('services.vigilmon.reports_heartbeat_url');
if ($url) {
Http::timeout(5)->get($url);
}
return Command::SUCCESS;
}
}
Register it in your scheduler:
// app/Console/Kernel.php (or routes/console.php in Laravel 11+)
Schedule::command('reports:generate')->dailyAt('06:00');
The heartbeat monitors the actual execution — not just whether the cron fires. If the command crashes halfway through, no ping is sent and you get alerted.
Step 4: Alerts via Slack or email
Configure how you want to be notified in Vigilmon:
For Slack:
- Create an incoming webhook in your Slack workspace
- In Vigilmon, go to Notifications → New Channel → Slack
- Paste the webhook URL
- Enable it on each monitor
For email:
- In Vigilmon, go to Notifications → New Channel → Email
- Enter your address or a distribution list
- Enable it on your monitors
Alert messages look like:
🔴 DOWN: yourdomain.com/up
Status: 500 Internal Server Error
Detected from: US-East, EU-West
3 minutes ago
Recovery notification:
✅ RECOVERED: yourdomain.com/up is back UP
Downtime: 14 minutes
Heartbeat alert:
🔴 MISSED: Daily Digest Job heartbeat
Expected every: 24 hours
Last ping received: 26 hours ago
Step 5: Public status page and README badge
A public status page answers the "is it just me?" question for your users — and reduces support noise during incidents.
In Vigilmon:
- Go to Status Pages → New Status Page
- Name it and select which monitors to display
- Copy the public URL and share it in your docs, footer, or error pages
You can also embed a live status badge in your GitHub README. Every monitor on Vigilmon has a badge endpoint at:
https://vigilmon.online/badge/{your-monitor-slug}
The badge shows current status (up, down, or degraded) and response time. Add it to your README.md:

Or as an HTML embed that links to your status page:
<a href="https://status.yourdomain.com">
<img src="https://vigilmon.online/badge/your-monitor-slug" alt="Uptime status">
</a>
What you've built
| What | How |
|---|---|
| HTTP uptime monitoring |
/up route + Vigilmon HTTP monitor |
| Database / cache health |
spatie/laravel-health checks |
| Queue job monitoring | Heartbeat ping at end of each job |
| Artisan command monitoring | Heartbeat ping on successful completion |
| Instant alerts | Slack or email webhook notifications |
| Public status page | Vigilmon status page |
| README status badge |
/badge/{slug} SVG embed |
The full setup costs $0 on Vigilmon's free tier and takes less time than tracking down a silent queue failure that's been running for two days.
Next steps
- Add a heartbeat for every critical background job, not just your daily ones
- Monitor response time trends — a gradual slowdown is often detectable hours before a full outage
- Add
UsedDiskSpaceCheckandRedisCheckto your health checks so disk-full events and cache failures surface before they cause downtime
Get started free at vigilmon.online.
Top comments (0)