Troubleshooting Guide
Symptoms, causes, and solutions for the most common issues encountered when installing, configuring, or running Online Casino Script.
Table of Contents
- Installation and setup
- Authentication
- WebSocket and real-time
- Queue and Horizon
- Email delivery
- Games
- Database
- Performance
- Deployment issues
- Debug procedures
Installation and setup
Missing PHP extensions
Symptoms: composer install or php artisan fails with extension not found or Call to undefined function.
Solution:
# Check which extensions are loaded
php -m | grep -E "bcmath|pdo_mysql|redis|gd|exif|fileinfo|zip|openssl|mbstring|pcntl"
# Install any missing extensions
sudo apt install -y php8.3-bcmath php8.3-mysql php8.3-redis php8.3-gd
php8.3-exif php8.3-zip php8.3-mbstring php8.3-fileinfo php8.3-pcntl
sudo systemctl restart php8.3-fpm
Required extensions: bcmath, pdo_mysql, redis, gd, exif, fileinfo, zip, openssl, mbstring, pcntl.
APP_KEY not set — 500 on all requests
Symptoms: Every request returns 500. Logs show RuntimeException: No application encryption key has been specified.
Solution:
php artisan key:generate
php artisan config:clear
JWT_SECRET not set — login returns 500
Symptoms: Login endpoint returns 500. Logs show TymonJWTAuthExceptionsSecretMissingException.
Solution:
php artisan jwt:secret
php artisan config:clear
APP_DEBUG=true throws RuntimeException in production
Symptoms: Application refuses to start in production. Error: APP_DEBUG must be false in production.
Cause: AppServiceProvider::validateProductionEnvironment() intentionally blocks startup when debug mode is on in APP_ENV=production.
Solution:
APP_DEBUG=false
php artisan config:clear && php artisan cache:clear
SESSIONSECURECOOKIE=true blocks local HTTP login
Symptoms: Login form submits but you are redirected back to the login page. Session cookie is not set.
Solution: For local development only:
SESSION_SECURE_COOKIE=false
Never set this to false in production.
Composer install fails with memory exhaustion
Symptoms: Killed or Allowed memory size exhausted during composer install.
Solution:
COMPOSER_MEMORY_LIMIT=-1 composer install --no-dev
Migration fails — syntax error or access violation
Cause 1: MySQL version is too old. Some migrations require MySQL 8.0+ (JSON columns, CHECK constraints).
mysql --version
Cause 2: Insufficient database privileges.
GRANT ALL PRIVILEGES ON online_casino.* TO 'casino_user'@'localhost';
FLUSH PRIVILEGES;
Cause 3: Partial migration run. Roll back and retry:
php artisan migrate:rollback --step=1
php artisan migrate
Authentication
401 Unauthorized — Token has expired
Symptoms: API calls return 401 {"message":"Token has expired"} after 15 minutes.
Cause: JWT access tokens expire after 15 minutes by design. The Vue API client (resources/js/api/client.ts) handles automatic refresh.
For server-to-server calls: Re-authenticate to obtain a fresh token.
To force re-login for all active sessions (e.g., after a security incident):
php artisan cache:clear # Clears the JWT blacklist, invalidating all tokens
Admin login blocked — 2FA required
Symptoms: Admin login returns 401 with correct credentials. Logs show 2FA required.
Solution: Log in from the machine where the TOTP app is configured.
If you have lost access to the TOTP device:
php artisan tinker
>>> AppModelsAdminUser::where('email', 'admin@casino.com')
... ->update(['two_factor_enabled' => false, 'two_factor_secret' => null]);
Then re-enable and re-scan the QR code after logging in.
429 Too Many Requests
Cause: Named rate limiters:
- Login: 5 requests / minute / IP
- Game play: 60 requests / minute / user
- Wallet writes (deposit/withdraw): 10 requests / minute / user
- Password reset: 5 requests / minute / IP
Solution: Wait for the Retry-After seconds. In development, temporarily disable the limiter in AppServiceProvider::configureRateLimiting().
WebSocket and real-time
WebSocket connection refused on port 8080
Symptoms: Browser console shows WebSocket connection to 'ws://...:8080/...' failed. Live balance updates and real-time game events don’t work.
Solution:
# Development
php artisan reverb:start
# Production — check status
sudo systemctl status casino-reverb
# Production — restart
sudo systemctl restart casino-reverb
# View logs
journalctl -u casino-reverb -f
WebSocket 403 — Origin not allowed
Symptoms: Reverb logs show Origin ... is not allowed. Browser gets 403 on WebSocket handshake.
Solution: Add your frontend URL to .env:
REVERB_ALLOWED_ORIGINS=https://casino.example.com,https://www.casino.example.com
php artisan config:clear
sudo systemctl restart casino-reverb
WebSocket connects then immediately drops (local development)
Cause: .env.example defaults REVERB_SCHEME=https. The local dev server uses HTTP.
Solution: In .env for local development:
REVERB_SCHEME=http
VITE_REVERB_SCHEME=http
Real-time events fire on the server but don’t reach the browser
Cause: Redis pub/sub is not working, or the broadcast queue worker is not processing.
# Check Horizon is running and processing broadcast events
php artisan horizon:status
# Check failed broadcast jobs
php artisan queue:failed | grep -i broadcast
# Retry failed jobs
php artisan queue:retry all
# Verify Redis pub/sub
redis-cli subscribe test
# In a second terminal: redis-cli publish test "hello"
Queue and Horizon
Queued jobs not processing
Symptoms: Emails not sent, bonus wagering not calculated, withdrawals stuck in pending.
Cause: Laravel Horizon is not running.
# Check status
php artisan horizon:status
# Start (development)
php artisan horizon
# Restart (production)
sudo systemctl restart casino-horizon
# View logs
journalctl -u casino-horizon -f
# Access the Horizon dashboard
# https://your-domain.com/horizon (admin auth required)
Horizon workers running but queue depth keeps growing
# Check queue depths in Redis
redis-cli llen casino_horizon_:queues:default
redis-cli llen casino_horizon_:queues:games
redis-cli llen casino_horizon_:queues:payments
# List failed jobs
php artisan queue:failed
# Retry all failed jobs
php artisan queue:retry all
# Flush failed jobs (permanent)
php artisan queue:flush
Horizon worker OOM — memory exhausted
Solution: Increase worker memory in config/horizon.php:
'supervisor-default' => [
'memory' => 256, // MB
...
],
Or set memory_limit = 256M in /etc/php/8.3/fpm/pool.d/casino.conf.
Email delivery
Emails queued but never delivered
Cause 1: Horizon is not running — emails are queued but never dispatched. See Queue and Horizon.
Cause 2: MAIL_MAILER=log — emails are written to log files only.
Solution: Configure SMTP in .env:
MAIL_MAILER=smtp
MAIL_HOST=smtp.your-provider.com
MAIL_PORT=587
MAIL_USERNAME=your@email.com
MAIL_PASSWORD=your-password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=noreply@casino.example.com
MAIL_FROM_NAME="Casino"
Test SMTP directly:
php artisan tinker
>>> Mail::raw('Test email', fn($m) => $m->to('test@example.com')->subject('Test'));
Check storage/logs/laravel.log for SMTP errors.
Emails go to spam
Solution:
- Configure an SPF record:
v=spf1 include:your-smtp-provider.com ~all - Configure DKIM via your SMTP provider (Mailgun, Postmark, AWS SES)
- Set
MAIL_FROM_ADDRESSto an address on your own domain
Games
Game shows “Loading…” indefinitely
Causes and solutions:
- WebSocket not connected — see WebSocket and real-time.
- Game disabled — Admin → Games → find the game → toggle Enabled.
- JS error — check Browser DevTools → Console.
- Check logs:
tail -f storage/logs/laravel.log | grep -i game
Game play returns 422 Unprocessable Entity
Cause: Bet amount format rejected. The backend expects a decimal string with up to 2 decimal places (e.g., "10.50").
Common validation failures:
- Amount has more than 2 decimal places
- Amount is below the game minimum bet
- Amount exceeds the game maximum bet or wallet balance
Verify bet limits in Admin → Games → [Game] → Configure.
Crash game multiplier does not update
Cause: WebSocket is not connected, or the crash-heartbeat queue is not being processed.
# Verify Horizon is processing the crash-heartbeat queue
php artisan horizon:status
# Look for a supervisor processing: games, crash-heartbeat
# Restart a stuck crash round (use with caution)
php artisan tinker
>>> AppModelsCrashRound::where('status', 'in_progress')
... ->update(['status' => 'crashed', 'crash_multiplier' => '1.00000000']);
Database
Database connection refused
# Check MySQL is running
sudo systemctl status mysql
sudo systemctl start mysql
# Test connection
mysql -h 127.0.0.1 -u root -p -e "SELECT 1"
# Check .env values
grep -E "^DB_" .env
# Clear cached config
php artisan config:clear
Slow queries / page timeouts
# Enable slow query log temporarily
mysql -u root -p -e "SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1;"
sudo tail -f /var/log/mysql/mysql-slow.log
Common hotspots: admin player list with uneager-loaded wallets, SiteSetting::get() called in a loop (use SiteSetting::all() once instead).
wallets.balance CHECK constraint violation
Symptoms: Wallet debit fails with Check constraint 'wallets_balance_check' is violated.
Cause: A debit was attempted that would result in a negative balance. This is a DB-level safety net.
This should not happen if all debits go through WalletService::debit(). Investigate the code path that bypassed the service layer.
Performance
High CPU on PHP-FPM workers
# 1. Verify OPcache is enabled
php -r "echo opcache_get_status() ? 'OPcache ON' : 'OPcache OFF';"
# 2. Ensure production caches are warm
php artisan config:cache && php artisan route:cache && php artisan view:cache
# 3. Verify APP_DEBUG=false
grep APP_DEBUG .env
# 4. Check for runaway Horizon workers
top -u www-data
Slow page loads — SPA navigation takes > 2 seconds
# Ensure caches are warm
php artisan config:cache && php artisan route:cache
# Log queries temporarily (development only)
php artisan tinker
>>> DB::enableQueryLog();
>>> // make the slow request, then:
>>> DB::getQueryLog();
Deployment issues
500 Internal Server Error after deploy
Cause: Stale config/route/view cache after code changes.
php artisan optimize:clear # Alias for all cache:clear commands
# Rebuild caches
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
# Ensure storage is writable
chmod -R 775 storage bootstrap/cache
# Run outstanding migrations
php artisan migrate --force
Assets returning 404 — blank SPA page
Cause: npm run build was not run, or public/build/ is missing.
npm ci
npm run build
ls public/build/manifest.json
CORS errors — Access-Control-Allow-Origin missing
CORS_ALLOWED_ORIGINS=https://casino.example.com
php artisan config:clear
Storage permission denied on file uploads
sudo chown -R www-data:www-data storage
sudo chmod -R 775 storage
php artisan storage:link
Debug procedures
Enable debug mode (staging only — never production)
# Staging only
APP_DEBUG=true
LOG_LEVEL=debug
php artisan config:clear
Never enable
APP_DEBUG=truein production — it exposes stack traces containing secrets in HTTP responses.
Reading logs
| Log | Path |
|---|---|
| Application | storage/logs/laravel.log |
| Queue workers | storage/logs/queue.log |
| Horizon | journalctl -u casino-horizon -f |
| Reverb (WebSocket) | journalctl -u casino-reverb -f |
# Watch all application logs
tail -f storage/logs/laravel.log storage/logs/horizon.log
# Find recent exceptions
grep -n "CRITICAL|ERROR" storage/logs/laravel.log | tail -20
Inspecting failed jobs
# List all failed jobs
php artisan queue:failed
# Retry a specific job
php artisan queue:retry <id>
# Retry all failed jobs
php artisan queue:retry all
# Delete a specific failed job
php artisan queue:forget <id>
# Flush all failed jobs
php artisan queue:flush
Health check
curl -s https://casino.example.com/api/health | python3 -m json.tool
Expected when fully healthy:
{
"status": "ok",
"database": "ok",
"redis": "ok",
"horizon": "ok",
"timestamp": "2026-01-01T00:00:00Z"
}
If any value is "error", investigate that service.
Clear all application caches
php artisan optimize:clear
# This runs: config:clear, route:clear, view:clear, cache:clear, event:clear
Test database and Redis connections from CLI
# Test MySQL
mysql -h $DB_HOST -P $DB_PORT -u $DB_USERNAME -p$DB_PASSWORD $DB_DATABASE -e "SELECT 1" 2>/dev/null
&& echo "MySQL: OK" || echo "MySQL: FAILED"
# Test Redis
redis-cli -h $REDIS_HOST -p $REDIS_PORT ${REDIS_PASSWORD:+-a $REDIS_PASSWORD} ping
| grep -q PONG && echo "Redis: OK" || echo "Redis: FAILED"
For installation instructions, see Installation Guide. For environment variable reference, see Configuration Reference. For production deployment, see Deployment Guide.