Production Deployment
This page covers a robust production setup: web server, queue workers, the scheduler and routine maintenance.
Deployment steps
From your server, in the project directory:
# 1. Pull the latest code (if using git)
git pull origin main
# 2. Install PHP dependencies (production, optimised)
composer install --no-interaction --prefer-dist --optimize-autoloader --no-dev
# 3. Install Node deps and build the UI
npm ci
npm run build
# 4. Run migrations
php artisan migrate --force
# 5. Cache config, routes, views and events
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
# 6. Restart the queue workers so they pick up new code
php artisan queue:restart
Queue workers (required)
Background jobs power inbound messages, campaigns, AI, social posting and emails. Run a supervised, always-on worker. The queues used by WhatsMine are:
whatsapp, broadcast, ai, social, leads, automation, default
Supervisor example
Create /etc/supervisor/conf.d/whatsmine-worker.conf:
[program:whatsmine-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/whatsmine/artisan queue:work --queue=whatsapp,broadcast,ai,social,leads,automation,default --tries=3 --max-time=3600
directory=/var/www/whatsmine
user=www-data
numprocs=2
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
redirect_stderr=true
stdout_logfile=/var/www/whatsmine/storage/logs/worker.log
stopwaitsecs=3600
Then:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start whatsmine-worker:*
Isolate heavy queues
For higher throughput, run separate workers per queue (e.g. one worker for broadcast, one for whatsapp) so a large campaign doesn't delay live inbox messages.
The scheduler (required)
A single cron entry drives all scheduled tasks (scheduled campaigns, billing reconciliation, trial expiry, social posting, digests, cleanup):
* * * * * cd /var/www/whatsmine && php artisan schedule:run >> /dev/null 2>&1
Verify it's firing in Admin → Cron Setup (the heartbeat turns green when the scheduler runs).
What the scheduler runs
| Task | Frequency |
|---|---|
| Scheduler heartbeat | every minute |
| Launch scheduled campaigns | every minute |
| Dispatch scheduled social posts | every minute |
| Billing sync (reconcile with gateways) | hourly |
| Expire trials | hourly |
| Sync WhatsApp templates from Meta | daily |
| Refresh social OAuth tokens | daily |
| Trial-ending emails | daily |
| Weekly performance digest | Mondays |
| Prune old usage meters / webhook events | monthly / weekly |
Web server (Nginx)
server {
listen 80;
server_name your-domain.com;
root /var/www/whatsmine/public;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
index index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.(?!well-known).* { deny all; }
}
Point the web root at the public/ directory (never the project root) and add a free SSL certificate with Let's Encrypt / Certbot.
Real-time at scale
If you self-host real-time with Laravel Reverb, run it as a supervised process:
php artisan reverb:start --host=0.0.0.0 --port=8080
…and proxy a WebSocket subdomain to it. Or use a hosted Pusher account to avoid running this process.
Database backups
php artisan db:backup # back up to the default disk
php artisan db:backup --disk=s3 # back up to S3
Schedule a daily backup via cron or the scheduler.
File permissions
sudo chown -R www-data:www-data storage bootstrap/cache
sudo chmod -R 775 storage bootstrap/cache
Updating
When a new version is released, follow the Updating guide.