# Server Cron Setup

The LMS application uses Laravel's scheduler to run periodic background jobs
(currently the daily certificate-expiry notifier at 06:00 server time).

For these scheduled tasks to actually run on the production server you must
install **one** system-level cron entry that calls `php artisan schedule:run`
every minute. Laravel itself decides which of its scheduled commands to dispatch
based on the rules in `app/Console/Kernel.php`.

## 1. Install the cron entry

SSH into the production server as the user that runs PHP/your web server
(commonly `www-data`, `nginx`, or your deploy user) and run:

```bash
crontab -e
```

Add this single line (replace `/var/www/LMS` with the real absolute path to
the project):

```cron
* * * * * cd /var/www/LMS && php artisan schedule:run >> /dev/null 2>&1
```

Save and exit. Cron will pick the change up automatically.

> Tip: if your server runs multiple PHP versions, point at the exact binary,
> e.g. `/usr/bin/php8.1 artisan schedule:run`.

## 2. Verify the schedule is registered

From the project root, list the registered scheduled tasks:

```bash
php artisan schedule:list
```

You should see an entry similar to:

```
0 6 * * *   php artisan certificates:notify-expired ........... Next Due: ...
```

## 3. Test the command manually

You can fire the certificate-expiry job manually (useful for QA):

```bash
php artisan certificates:notify-expired
```

It is idempotent: each certificate is notified at most once because
`last_expiry_notified_at` is set on the row after a successful send.

## 4. Optional: log the cron output

If you want a paper trail for debugging, replace `>> /dev/null 2>&1` with a log
file owned by the same user, e.g.:

```cron
* * * * * cd /var/www/LMS && php artisan schedule:run >> /var/log/lms-cron.log 2>&1
```

## What happens without this cron entry

Without the system cron entry above the Laravel scheduler will never fire on
the server, which means the **expired-certificate notification email will not
be sent**. The rest of the certificate flow (issuance, approval, viewing,
downloading, expiry badge in UI / overlay on image) keeps working without
cron because it is triggered by user requests, not by the scheduler.
