Heartbeats for the parts that run in the background
The scheduler. Laravel’s documentation says that when you use its scheduler “we only need to add a single cron configuration entry to our server that runs the schedule:run command every minute”:
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1If that one line disappears, because a server was rebuilt or the crontab was overwritten, every scheduled task stops at once and nothing returns an error. The framework can ping a URL itself. Laravel documents pingBefore, thenPing, pingOnSuccess and pingOnFailure, and says a failure means the command “terminated with a non-zero exit code”. Create a scheduled-task check in Beamfar, copy its ping URL, and attach it to the jobs that matter:
Schedule::command('reports:send')
->dailyAt('02:00')
->pingBefore($pingUrl . '/start')
->pingOnSuccess($pingUrl)
->pingOnFailure($pingUrl . '/fail');The /start and /fail calls are optional: Beamfar measures the run time from them and opens an incident at once on a failure ping, and a missed ping after the period plus the grace time also opens one. For a plain “is the scheduler alive” signal, add a task that does nothing but ping every five minutes with thenPing.
Maintenance mode hides the heartbeat. The documentation says scheduled tasks “will not run when the application is in maintenance mode” unless a task calls evenInMaintenanceMode(). A site left in maintenance mode therefore produces both a visible outage and a missed heartbeat. That is correct, but it is two alerts, so know which one you are reading.
The queue. Queue workers are long-lived processes. Laravel Forge’s documentation says to restart them from the deployment script so they pick up new code, with php artisan queue:restart, and warns that the command “requires a cache driver that persists data between requests”; with the array driver it “will fail silently”. On Laravel Forge, Supervisor watches the workers and restarts them if they crash. None of this shows on a page. Laravel’s scheduling documentation includes a queued Heartbeat job dispatched every five minutes; make that job call the ping URL when it runs, and a stopped worker, a full queue or a dead scheduler all end in the same missed ping.
What the outside request tells you
Maintenance mode answers 503. With php artisan down, requests get an HTTP 503. The --retry option sets the Retry-After header, though “browsers generally ignore” it. A planned release that takes the app down for a few seconds will show in the evidence as a short 503, which the two-region rule treats like any other failure. Keep releases short, and say so in the incident communication when they are not. The classic outage is the other one: php artisan down was run and php artisan up never was.
A cached configuration loses its environment. After config:cache, Laravel does not load the .env file, so the env function “will only return external, system level environment variables”. A deploy that caches configuration on a server that does not have those variables can fail every request that needs them. The page check shows the status and timing in the incident.
Debug mode in production. The documentation says APP_DEBUG should always be false in production, because with true in production “you risk exposing sensitive configuration values” to users. An outside check that requires text cannot prove a negative, so this belongs in the deploy checklist, not the monitor.
The checks that matter most
| Check | What it catches here | How to set it up in Beamfar |
|---|---|---|
| Page with required text | A 500 or 503 page, a blank page, a maintenance page | The page check; require a phrase from the layout that always prints |
| A route that boots the framework | The application failing while a cached page answers | An API check on a lightweight route you add, status 200 and a response-time limit |
| Scheduler heartbeat | The cron entry removed or the scheduler failing | A scheduled-task check and thenPing on a five-minute task |
| Job heartbeat | A task that fails or runs too long | pingBefore, pingOnSuccess and pingOnFailure on the task |
| Queue heartbeat | A worker that is stopped or running old code | A queued job that pings when it runs |
| Certificate, domain, DNS | Expiry and records changed | Automatic on every site |
Set it up
- Add the site’s host name in the portfolio. The page, certificate and DNS checks start at once.
- Require a phrase from the layout that every page prints, so a blank page or an error view fails the page check.
- Add a small route to the application, such as a health route that touches the database, and an API check for it: status 200 and a response-time limit.
- Add a scheduled-task check for the scheduler with a period of five minutes and a short grace time, and attach
thenPingwith its ping URL to a five-minute task. - Add the job-level pings to reports, backups and imports, and a second scheduled-task check for the queue that a queued job answers.
- Send alerts to a person who can redeploy, and agree beforehand what a missed heartbeat during planned maintenance means.
What an outside check cannot see
It cannot see exceptions in the logs, failed jobs that the queue retried silently, a slow query or a worker leaking memory. It does not know your routes unless you give it one. Put an error tracker and the framework’s own logging next to the monitor, and use the monitor for what they cannot do: tell you from outside that the whole thing is gone, and that a background task never reported in.
How Beamfar helps
Beamfar is not a Laravel package and has no agent to install. It calls a site down only when two regions agree, runs API checks with assertions on the status, headers, JSON values and response time, and receives heartbeat pings with a start and a fail signal. An incident keeps the status code, redirects, certificate and DNS answer. It has been tried on test pages only and has no customers yet.
Questions
How do I monitor the Laravel scheduler?
Add a scheduled-task check, then attach its ping URL to a task with thenPing, or to important jobs with pingBefore, pingOnSuccess and pingOnFailure. A missed ping opens an incident.
Why is my Laravel site returning 503?
The application is probably in maintenance mode, which Laravel answers with an HTTP 503. Run php artisan up if the release is finished.
Does a page check catch a stopped queue worker?
No. The pages keep answering. Use a queued job that pings a heartbeat URL, and restart workers from the deploy script.
Does this work for PHP apps that are not Laravel?
Yes. Any app reachable over HTTP(S) gets the page, API and certificate checks, and any cron job can call a ping URL with curl.
Sources
Read on . Standards and vendor pages change; confirm the current text before you rely on it.