When php artisan route:cache runs, Laravel writes the compiled route table to bootstrap/cache/routes-v7.php. If that file is stale, empty, built from the wrong release, or missing from one app server, users can hit routes that no longer match your deployed code.
The practical monitoring goal is simple: prove bootstrap/cache/routes-v7.php was rebuilt after the current deploy, on every production node that serves traffic. A green deploy is not enough if the route cache came from yesterday’s release.
How route:cache writes bootstrap/cache/routes-v7.php
Laravel reads the route definitions in routes/web.php, routes/api.php, route groups, middleware assignments, name prefixes, and controller references. It then writes a compiled PHP array to bootstrap/cache/routes-v7.php. The version number in the filename can change across Laravel versions, but the job is the same: skip route parsing on every request and load the cached route table instead.
That speedup is useful in production. The risk is that the file is a snapshot. If a deploy adds /billing/webhooks, renames a controller, or changes a route parameter after the cache was built, Laravel will keep serving the old snapshot until route:cache runs again. In rolling deploys, one server can have the new code and another can still have the old route cache.
Common route cache failure modes
Stale cache after rolling deployments. A node that has not received the new release can still run route:cache and write routes from the old codebase. Traffic then behaves differently depending on which server handles the request. That is painful to debug because the route exists on one node and disappears on another.
The cache file exists but is not from this deploy. Checking only for bootstrap/cache/routes-v7.php is too weak. The file may exist because last week’s deploy created it. Compare its modification time with your deploy timestamp and alert when the file is older than the release that should have rebuilt it.
Named route conflicts get frozen into the cache. Duplicate route names, stale controller references, and old middleware assignments can hide until a user hits the path or the app calls route(). The cache file turns a routing mistake into a production snapshot.
The command fails but the deploy continues. Permission problems in bootstrap/cache, missing controllers, syntax errors, or wrapper scripts that swallow exit codes can leave production with no fresh route cache. If the deploy continues anyway, the first alert often comes from a user, not from CI.
Building visibility into route:cache
The safest pattern is to make the deploy script fail loudly if the cache file is missing or empty after route:cache runs:
php artisan route:cache
route_file="bootstrap/cache/routes-v7.php"
if [ ! -s "$route_file" ]; then
echo "route cache file missing or empty"
exit 1
fi
That check catches the command failing on the node running the deploy. It does not catch a node that never received the deploy at all - for that, add a small internal health endpoint that checks file age against your deploy window:
Route::get('/health/routes', function () {
$routeFile = base_path('bootstrap/cache/routes-v7.php');
if (! is_file($routeFile) || filesize($routeFile) === 0) {
return response()->json(['status' => 'error', 'reason' => 'route_cache_missing'], 503);
}
$ageSeconds = time() - filemtime($routeFile);
if ($ageSeconds > 3600) {
return response()->json(['status' => 'warning', 'route_cache_age' => $ageSeconds], 503);
}
return response()->json(['status' => 'ok', 'route_cache_age' => $ageSeconds]);
})->middleware('auth.basic');
Keep this endpoint private. It is a production safety check, not a public status page.
Detecting when route:cache fails
Use two signals together. First, your deploy script should fail immediately if route:cache exits non-zero or bootstrap/cache/routes-v7.php is missing. Second, schedule a lightweight verification that Crontinel can watch on an ongoing basis.
That scheduled check catches the cases your deploy script misses: a command skipped on one node, a cache file written by the wrong user, or a deploy wrapper that continued after Artisan failed.
// routes/console.php
Schedule::call(function () {
$routeFile = base_path('bootstrap/cache/routes-v7.php');
if (! is_file($routeFile) || time() - filemtime($routeFile) > 7200) {
throw new RuntimeException('route cache missing or stale');
}
})->everyFiveMinutes()->name('route-cache-freshness-check');
This does not replace a deploy hook. It gives you a second line of defense when a release tool skips a step.
Quick setup with Crontinel
Crontinel’s package hooks into ScheduledTaskStarting, ScheduledTaskFinished, and ScheduledTaskFailed automatically once installed - no ping URL or config needed. Create a Cron monitor in the dashboard named route-cache-freshness-check; when the closure above throws, Crontinel reports the failed run.
Set the alert window to match your rollout. For a normal CI deploy, 15 to 30 minutes of staleness tolerance is usually enough. For rolling deploys, give each node enough time to finish before the freshness check flags it.
Do not monitor route cache as a generic cron job. Monitor it as a deploy-critical signal tied to the release that should have rebuilt bootstrap/cache/routes-v7.php.