Scheduling

Manual verification and pruning work for development, but production systems need automation. Laravel's task scheduler lets you run audit:verify and audit:prune on a recurring basis — no custom cron entries required beyond the standard Laravel scheduler.

Add both commands to routes/console.php (Laravel 11+ style):

use Illuminate\Support\Facades\Schedule;

Schedule::command('audit:verify --notify')->hourly();
Schedule::command('audit:prune')->daily();

This gives you:

  • Hourly verification — detects tampering within 60 minutes and sends notifications through your configured channels
  • Daily pruning — removes audit logs older than your configured retention period (default: 90 days)

Server Cron Entry

Laravel's scheduler requires a single cron entry on your server. If you haven't set this up already:

* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1

This runs the Laravel scheduler every minute. The scheduler itself determines which commands are due.

Custom Schedules

Verification Frequency

The right verification frequency depends on how critical tamper detection is for your use case:

// High security -- check every 15 minutes
Schedule::command('audit:verify --notify')->everyFifteenMinutes();

// Standard -- check every hour
Schedule::command('audit:verify --notify')->hourly();

// Low priority -- check once a day
Schedule::command('audit:verify --notify')->daily();

Different Retention for Different Models

Schedule multiple prune commands with different retention periods:

// Keep financial logs for 7 years
Schedule::command('audit:prune --days=2555 --type="App\Models\Transaction"')->daily();

// Keep general activity logs for 90 days
Schedule::command('audit:prune --days=90 --type="App\Models\Post"')->daily();

// Prune everything else at the default retention
Schedule::command('audit:prune')->daily();

Time-Based Scheduling

Run heavy operations during off-peak hours:

Schedule::command('audit:verify --notify')->hourly();
Schedule::command('audit:prune')->dailyAt('03:00');

Environment-Specific Scheduling

Use environments() to restrict scheduling to specific environments:

Schedule::command('audit:verify --notify')
    ->hourly()
    ->environments(['production', 'staging']);

Schedule::command('audit:prune')
    ->daily()
    ->environments(['production']);

Preventing Overlaps

For large audit tables, verification can take time. Prevent overlapping runs with withoutOverlapping():

Schedule::command('audit:verify --notify')
    ->hourly()
    ->withoutOverlapping();

Schedule::command('audit:prune')
    ->daily()
    ->withoutOverlapping();

Output Logging

Capture command output for debugging:

Schedule::command('audit:verify --notify')
    ->hourly()
    ->appendOutputTo(storage_path('logs/audit-verify.log'));

Schedule::command('audit:prune')
    ->daily()
    ->appendOutputTo(storage_path('logs/audit-prune.log'));

Full Example

A production-ready routes/console.php combining all recommendations:

use Illuminate\Support\Facades\Schedule;

Schedule::command('audit:verify --notify')
    ->hourly()
    ->withoutOverlapping()
    ->environments(['production'])
    ->appendOutputTo(storage_path('logs/audit-verify.log'));

Schedule::command('audit:prune')
    ->dailyAt('03:00')
    ->withoutOverlapping()
    ->environments(['production'])
    ->appendOutputTo(storage_path('logs/audit-prune.log'));