---
title: "Laravel health check endpoint and /up | Logdash"
description: "Laravel 11 added a /up health route, but it only proves the framework booted. Add a route with a database check, a 503, and a monitor watching it."
url: https://logdash.io/health-check/laravel
---

# Laravel health check endpoint and the built-in /up route

Laravel 11 and 12 register a health route at /up out of the box, but it only proves the framework booted, so add a route that runs one database query and returns 503 when it fails.

Open bootstrap/app.php in any Laravel 11 or 12 app and the health route is already there, passed as an argument: Application::configure()->withRouting(web: ..., commands: ..., health: '/up'). Laravel registers a GET route on that path, fires a DiagnosingHealth event and returns 200 unless a listener throws, in which case you get a 500\. Nothing subscribes to that event in a fresh app, so out of the box /up answers 200 whenever PHP-FPM is alive and the framework boots.

So the useful question is not whether you have a health endpoint. It is whether yours can fail. A booted Laravel app that can no longer reach MySQL still returns 200 from /up while every request to a real controller throws a QueryException, and the uptime monitor watching the site records a clean day through the incident. The check has to touch the thing the app needs: one cheap query, and a status code outside the success range when that query does not come back.

## A route that fails when the database does

 routes/web.php 

```php
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Route;

Route::get('/health', function () {
    try {
        DB::select('select 1');
    } catch (\Throwable $e) {
        report($e);

        // 503, so a booted app with a dead database reads as down.
        return response()->json(['ok' => false, 'error' => 'database'], 503);
    }

    return response()->json(['ok' => true]);
});
```

If you would rather keep the built-in path, hook the event instead. In AppServiceProvider::boot, Event::listen(DiagnosingHealth::class, fn () => DB::select('select 1')). A throw from that listener turns /up into a 500, and a monitor reads a 500 the same way it reads a 503.

## What belongs in the check

- The default connection, once, with something as cheap as select 1\. DB::select uses the same pool and the same credentials your controllers use, so it fails for the same reasons they do.
- Redis or the queue, only when the app cannot serve a page without them. A slower cache miss is not an outage.
- Nothing that reaches a payment provider or an object store. That turns their bad night into your pager, and their night is not yours to fix.
- Under a second, end to end. The Logdash pinger times out at 10 seconds and records the check as down, so a slow health route becomes a false alarm.
- A body of two keys. No app version, no queue depth, no config values. Assume anyone can curl the URL, because they can.

## Point a monitor at it

1. **Check both directions** Load /health in a browser, then break the database credentials in a staging .env and load it again. You want your JSON with a 503, so confirm APP\_DEBUG is false anywhere a monitor can reach.
2. **Create the monitor** Add a monitor in Logdash, paste the URL, and the first check runs straight away. Every check stores the status code and the response time, at 5-minute intervals on the free plan, 1 minute on Builder and 15 seconds on Pro.
3. **Prove the alert** Stop the database container and wait one interval. The status code leaves the 200-399 window, the monitor flips to down on the transition, and Telegram gets a message naming the endpoint and the code.

## artisan down does not take /up down

Laravel excludes the built-in health path from maintenance mode on purpose: withRouting registers it through PreventRequestsDuringMaintenance::except(). Run php artisan down and every page returns 503 while /up carries on returning 200\. That is right for a load balancer, which should keep routing to a pod that is deliberately in maintenance, and wrong for an uptime monitor, which will report the site as fine while nobody can buy anything. A custom route in routes/web.php goes through that middleware and returns 503 like everything else, so the monitor follows your deploys. Decide which of the two you want before you pick the URL.

### Does Laravel have a built-in health check route? 

Yes, since Laravel 11\. bootstrap/app.php passes health: '/up' to withRouting, which registers a GET route that fires the DiagnosingHealth event and returns 200 unless a listener throws. Laravel 12 ships the same line.

### What is the health check URL in Laravel? 

/up by default. The path is whatever string you pass to health: in bootstrap/app.php, so health: '/healthz' moves it. A route of your own can live anywhere, and /health and /healthz are the two conventions worth picking between.

### Is there a Laravel health check package? 

spatie/laravel-health is the well-known one and it earns its keep once you have a dozen named checks with thresholds and a dashboard. For one query and one status code, a closure in routes/web.php is less to maintain.

### Is there an artisan command for health checks? 

Not in the framework. php artisan about prints environment and cache state locally, and spatie/laravel-health adds health:check for cron or CI. Neither is what a monitor should use, because a monitor needs an HTTP status code.

### What status code should a Laravel health check return? 

200 when the app can serve, 503 when it cannot. Logdash marks a monitor down on anything outside 200-399, so the status code is the difference between a chart nobody reads and an alert that arrives.

## Keep reading

[All health check guides](https://logdash.io/health-check): One page per framework, each with the endpoint code and the monitor that watches it.

[Elixir Phoenix health check endpoint with Ecto and the cluster](https://logdash.io/health-check/phoenix): Phoenix ships no health route, so you write a controller action that runs one Ecto query, returns 200 with the number of connected nodes, and answers 503 when the query raises.

[Spring Boot health check endpoint with Actuator](https://logdash.io/health-check/spring-boot): Add spring-boot-starter-actuator and Spring Boot serves /actuator/health for you, returning 200 while every contributor is UP and 503 the moment one of them goes DOWN.

[.NET health check endpoint in ASP.NET Core](https://logdash.io/health-check/dotnet): ASP.NET Core has health checks built in: AddHealthChecks() registers them, MapHealthChecks("/health") serves them, and an unhealthy result returns 503 with no extra configuration.

[Uptime monitoring](https://logdash.io/features/monitoring): HTTP checks as often as every 15 seconds. When one fails, Telegram tells you before your users do.
