skip to content

In Laravel 13, what does the built-in /up route check, how do you add a database check, and what does it return during maintenance?

level: middleimportance: must knowfreq 45%

answer

  1. withRouting(health: '/up')
  2. 200 if the app boots, else 500
  3. DiagnosingHealth event, throw to fail
  4. JSON status up or down
  5. excluded from maintenance mode

basics

~20 s

/up, registered by withRouting(health: '/up'), returns 200 when the app boots and every DiagnosingHealth listener succeeds, and 500 if a listener throws. It is excluded from maintenance mode, so it stays 200 while the site is down.

solid answer

~40 s

The skeleton's `bootstrap/app.php` passes `health: '/up'` to `withRouting()`, which registers a GET route outside the `web` group and adds the path to the maintenance middleware's except list. The route dispatches `Illuminate\Foundation\Events\DiagnosingHealth`; with no listeners it only proves the app booted and returned 200. To check the database, write a listener for `DiagnosingHealth` that runs a cheap query and throws when it fails. Any exception is reported and the route returns 500 with an "experiencing problems" page, or, since 13.6, `{"status":"down"}` for requests that expect JSON (`{"status":"up"}` when healthy). With `APP_DEBUG=true` the exception is rethrown instead. Because the route is excluded from maintenance, `/up` keeps returning 200 during `php artisan down`, so it does not signal maintenance.

go deeper

for a junior

Recall that /up is registered in bootstrap/app.php, returns 200 when the app boots and 500 when a health listener throws.

for a middle

Explain DiagnosingHealth listeners, the JSON and HTML responses, and why the route skips the web group and maintenance mode.

for a senior

Keep health listeners cheap, decide what they should cover, and account for /up staying 200 during maintenance in your monitoring.

for a principal

Align what /up proves with how load balancers and monitors use it, so a failing dependency pulls traffic only when that helps.

## Where the route comes from A new Laravel 13 app's `bootstrap/app.php` contains: ```php ->withRouting( web: __DIR__.'/../routes/web.php', commands: __DIR__.'/../routes/console.php', health: '/up', ) ``` When `health` is a string, `ApplicationBuilder::withRouting()` does two things: 1. registers `Route::get($health, ...)` in the routing callback, **outside** the `web` middleware group, so the route has no session, cookies or CSRF handling; 2. calls `PreventRequestsDuringMaintenance::except($health)`, adding the path to the maintenance exemptions. Changing the URI is a one-word edit, for example `health: '/status'`; omitting `health` removes the route. ## What the route does The route's closure: - dispatches `Illuminate\Foundation\Events\DiagnosingHealth`, an empty event class; - catches any `Throwable` a listener throws; if debug mode is on it **rethrows**, otherwise it calls `report($e)` and remembers the message; - returns status **200** when nothing was thrown and **500** otherwise; - for requests that expect JSON, returns `{"status": "up"}` or `{"status": "down"}` (added in Laravel 13.6); - otherwise returns a small HTML page reading "Application up" or "Application experiencing problems". With no listeners, the route proves only that PHP ran, the framework booted, providers registered and routing works. If the app cannot boot at all, the request fails with an error response (normally a 500) before reaching the route, which is still a failing check. ## Adding a database check The docs' pattern is to listen for `DiagnosingHealth` and throw when something is wrong: ```php <?php namespace App\Listeners; use Illuminate\Foundation\Events\DiagnosingHealth; use Illuminate\Support\Facades\DB; class CheckDatabaseHealth { public function handle(DiagnosingHealth $event): void { DB::select('select 1'); } } ``` If the query throws, the route returns 500 and the exception is reported through the normal exception handler. Keep listeners cheap and bounded: every monitor, load balancer or orchestrator probe runs them. ## Behaviour during maintenance Because `withRouting()` exempts the health path, `/up` bypasses the maintenance middleware. The payload that `php artisan down` writes includes the excluded paths, so the prerendered-page stub lets `/up` through as well. The result: | Situation | `/up` response | |---|---| | normal operation | 200 | | a `DiagnosingHealth` listener throws | 500 | | `php artisan down` active, listeners pass | **200** | | app cannot boot | an error response, normally 500 | That is deliberate: a load balancer that watches `/up` keeps servers in rotation during maintenance, so visitors see the maintenance page rather than a load-balancer error. The flip side is that `/up` cannot tell a monitor the site is in maintenance; if you need that, check maintenance state separately, for example with `app()->isDownForMaintenance()` in your own endpoint. ## Using /up from outside - **Load balancers** usually only look at the status code, so 200 versus 500 is all that matters to them. - **Uptime monitors** that send `Accept: application/json` get the compact `{"status": ...}` body, which is easier to parse than the HTML page. - **Custom paths** are common when something else already owns `/up`; change the `health` argument rather than adding a second route. - **Reported exceptions** from failing listeners reach the normal log channels, so a failing probe leaves a trace to investigate. ## Interview pitfalls - Claiming `/up` checks the database by default. It checks nothing beyond booting unless you add listeners. - Forgetting that debug mode rethrows, so a local run shows an error page rather than the health page. - Adding slow checks, such as external API calls, to a route probed every few seconds. ## Version note The route and the `DiagnosingHealth` event are part of the slim skeleton; the JSON response for requests that expect JSON arrived in Laravel 13.6.0. Earlier 13.x releases always returned the HTML page.

  • Why does the Laravel /up route not start a session or check CSRF?
    `withRouting()` registers the health route outside the `web` middleware group, so the session, cookie encryption and CSRF middleware that the group adds never run for it. Only global middleware applies, which keeps each probe cheap and stateless.
  • With APP_DEBUG=true, what does the Laravel /up route do when a DiagnosingHealth listener throws?
    It rethrows the exception instead of catching it, so the exception handler renders its usual debug error page with a 500. With debug off, the route reports the exception and returns its own 500 health page or `{"status":"down"}` JSON.

saying these in an interview costs you the question

  • /up checks the database and cache by default
  • /up returns 503 while the app is in maintenance mode
  • Health checks are added by editing a HealthController
  • A DiagnosingHealth listener fails the check by returning false
  • The /up route runs inside the web middleware group