Website & Admin Panel
Cron & Queue Workers
Two background processes keep the platform running smoothly: the scheduler (timed tasks) and the queue worker (jobs like push notifications and emails). Set up both.
1. The scheduler (cron)
Add a single cron entry that runs every minute. Laravel decides what actually needs to run.
php. Cron does not read your shell profile, so it gets the system default, which on shared hosting is often an older version than your site uses. Combined with >> /dev/null below, a wrong binary fails silently, forever β the classic "my scheduled tasks never ran and nothing was logged" case.Find the right binary and the app's real path first:
cd /path/to/your/app && pwd # the exact app path
ls /usr/bin/php8* /opt/alt/php8*/usr/bin/php 2>/dev/null # available binaries
which php # what plain "php" would give cron
Then build the entry from those two values:
# shared hosting (CloudLinux / Hostinger layout)
* * * * * cd /home/USER/domains/YOURDOMAIN/public_html/shop && /opt/alt/php84/usr/bin/php artisan schedule:run >> storage/logs/cron.log 2>&1
# cPanel with a system PHP
* * * * * cd /home/USER/bringo && /usr/local/bin/php artisan schedule:run >> storage/logs/cron.log 2>&1
# VPS
* * * * * cd /var/www/bringo && /usr/bin/php artisan schedule:run >> storage/logs/cron.log 2>&1
storage/logs/cron.log rather than /dev/null. Leave it that way for the first day: after a few minutes tail storage/logs/cron.log tells you whether cron is running at all and whether it found PHP. Once you have seen it work, switch the tail to >> /dev/null 2>&1 if you would rather not grow a log file β and remember to clear the log occasionally if you keep it.Verify it by hand before trusting cron β run the exact command from the entry, with the full binary path:
cd /path/to/your/app && /opt/alt/php84/usr/bin/php artisan schedule:run
You should see either a list of tasks that ran, or "No scheduled commands are ready to run." Anything else β a PHP version error, a missing vendor/ β would have been invisible inside cron.
On cPanel/hPanel, add the entry through Cron Jobs in the panel rather than crontab -e.
What the scheduler runs
| Task | Schedule | What it does |
|---|---|---|
notifications:dispatch-scheduled | Every minute | Sends admin notification campaigns that are due. |
partner:expire-offers | Every minute | Expires delivery-assignment offers no rider answered. |
food:auto-accept | Every minute | Auto-accepts food orders whose seller auto-accept timer has elapsed. |
accounts:process-deletions | Hourly | Processes customer account-deletion requests that have become due. |
cashback:process-seasonal | Hourly | Credits pending seasonal-campaign cashback for delivered orders and releases reservations of cancelled orders. Until this runs, customers see the amount as βPendingβ. |
loyalty:tick | Hourly | Credits matured loyalty points and expires old ones. |
price-alerts:scan | Hourly | Notifies customers when a wishlisted product drops in price. |
cashback:process-monthly | Daily 02:15 | On your configured day-of-month, sums last month's spend per customer and credits monthly cashback. |
cashback:expire | Daily 02:30 | Expires cashback that passed its expiry date. |
wallet:expire-stale-topup-intents | Daily 03:00 | Cleans up abandoned wallet top-up payments. |
subscriptions:process | Daily 05:00 | Generates the day's subscription orders. |
affiliates:credit | Daily 06:00 | Credits matured affiliate commissions. |
partner:document-expiry-reminders | Daily 08:30 | Reminds delivery partners about expiring documents. |
2. Triggering tasks over HTTP (no shell access needed)
Most scheduled tasks can also be fired through a URL β useful on shared hosting without cron access, with external cron services (cron-job.org, EasyCronβ¦), or for testing a task right now. The single /cron/run URL runs the whole schedule, so it covers every task including the ones with no individual URL.
Set the secret key
Add a long random secret to your .env, then refresh the config cache:
CRON_SECRET=paste-a-long-random-string-here
php artisan config:clear && php artisan config:cache
CRON_SECRET like a password. Anyone who has it can trigger these tasks. Requests with a wrong or missing key get 403.URL format
https://your-domain.com/cron/run/{task}?key=CRON_SECRET
The most important URL β run everything that is due (point your external cron service at this one, every minute or every 5 minutes):
https://your-domain.com/cron/run?key=CRON_SECRET
All task URLs
Replace your-domain.com and CRON_SECRET with your values. Prefix each with https://your-domain.com/cron/run/:
| URL | Optional parameters |
|---|---|
cashback:process-seasonal?key=β¦ | β |
cashback:process-monthly?key=β¦ | &force=1 run before the scheduled day Β· &month=2026-06 settle a specific month (YYYY-MM) |
cashback:expire?key=β¦ | β |
loyalty:tick?key=β¦ | &credit=1 or &expire=1 to run only one half |
subscriptions:process?key=β¦ | &date=2026-07-21 process a specific day |
affiliates:credit?key=β¦ | &date=2026-07-21 |
notifications:dispatch-scheduled?key=β¦ | β |
wallet:expire-stale-topup-intents?key=β¦ | &hours=12 override the staleness window |
partner:expire-offers?key=β¦ | β |
partner:document-expiry-reminders?key=β¦ | &date=2026-07-21 |
price-alerts:scan?key=β¦ | β |
Example β settle seasonal cashback immediately instead of waiting for the hourly run:
https://your-domain.com/cron/run/cashback:process-seasonal?key=CRON_SECRET
food:auto-accept and accounts:process-deletions, which run through the full schedule instead) returns 404. Each response reports the exit code and runtime, so your cron service can alert on failures.X-Cron-Key header instead of ?key=. Prefer the header where your cron service supports it β a key in the query string ends up in Referer headers, browser history and proxy logs.3. The queue worker
Jobs (push notifications, emails) are pushed to a queue and processed by a worker.
On a VPS β use Supervisor (recommended)
sudo apt install -y supervisor
Create /etc/supervisor/conf.d/bringo-worker.conf:
[program:bringo-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/bringo/artisan queue:work database --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
user=www-data
numprocs=1
redirect_stderr=true
stdout_logfile=/var/www/bringo/storage/logs/worker.log
stopwaitsecs=3600
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start bringo-worker:*
On shared hosting β cron fallback
If you can't run a permanent process, add a second cron job that drains the queue periodically. The same rule applies β full path to PHP, real app path:
* * * * * cd /home/USER/domains/YOURDOMAIN/public_html/shop && /opt/alt/php84/usr/bin/php artisan queue:work --stop-when-empty >> storage/logs/worker.log 2>&1
--stop-when-empty makes the worker exit once the queue is drained, so the next minute's run starts cleanly instead of stacking up processes. If your host limits how long a process may live, add --max-time=50 so it always finishes inside the minute.
php artisan queue:restart (Supervisor will respawn it).