Documentation Developer Guide Troubleshooting Guide

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

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

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:

  1. Configure an SPF record: v=spf1 include:your-smtp-provider.com ~all
  2. Configure DKIM via your SMTP provider (Mailgun, Postmark, AWS SES)
  3. Set MAIL_FROM_ADDRESS to an address on your own domain

Games

Game shows “Loading…” indefinitely

Causes and solutions:

  1. WebSocket not connected — see WebSocket and real-time.
  2. Game disabled — Admin → Games → find the game → toggle Enabled.
  3. JS error — check Browser DevTools → Console.
  4. 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=true in 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.