Deployment Guide
This guide covers deploying Online Casino Script to a production Ubuntu 22.04 LTS server, including server provisioning, Nginx configuration, SSL, systemd services, monitoring, backups, and scaling.
For the initial Docker-based setup or a step-by-step bare-metal install, see the Installation Guide. This guide focuses on production operations.
Table of Contents
- Server provisioning
- Application deployment
- Nginx configuration
- PHP-FPM tuning
- MySQL tuning
- Redis configuration
- Systemd services (Horizon + Reverb)
- Cron schedule
- SSL certificate
- Environment variables for production
- Health and monitoring
- Log management
- Backup strategy
- Zero-downtime deploys
- Horizontal scaling
- Security checklist
- Pre-launch checklist
1. Server provisioning
Recommended hardware
| Traffic level | CPU | RAM | Disk | Bandwidth |
|---|---|---|---|---|
| Up to 500 concurrent players | 4 vCPUs | 8 GB | 50 GB SSD | 100 Mbps |
| Up to 5,000 concurrent players | 8 vCPUs | 16 GB | 100 GB NVMe | 1 Gbps |
| 5,000+ concurrent players | See Section 15 — Horizontal scaling |
Operating system: Ubuntu 22.04 LTS
Recommended providers: Hetzner, DigitalOcean, AWS EC2, OVH, Linode. Verify the provider allows gambling-related workloads — some providers prohibit this in their terms of service.
Software versions
| Software | Minimum version |
|---|---|
| PHP | 8.3+ |
| Nginx | 1.24+ |
| MySQL | 8.0+ (or MariaDB 10.6+) |
| Redis | 7.0+ |
| Node.js | 20+ (build step only) |
| Certbot | Latest |
Install system dependencies
sudo apt update && sudo apt upgrade -y
# PHP 8.3 and required extensions
sudo add-apt-repository ppa:ondrej/php -y
sudo apt update
sudo apt install -y php8.3-fpm php8.3-cli php8.3-mysql php8.3-redis
php8.3-bcmath php8.3-curl php8.3-fileinfo php8.3-gd php8.3-mbstring
php8.3-openssl php8.3-pcntl php8.3-tokenizer php8.3-xml php8.3-zip
# Nginx
sudo apt install -y nginx
# MySQL 8
sudo apt install -y mysql-server
sudo mysql_secure_installation
# Redis 7
sudo apt install -y redis-server
# Composer
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
# Node.js 20 (build step only)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
# Certbot (SSL)
sudo apt install -y certbot python3-certbot-nginx
2. Application deployment
# Create application directory
sudo mkdir -p /var/www/casino
sudo chown -R www-data:www-data /var/www/casino
# Clone the repository
cd /var/www/casino
git clone <your-repo-url> . --depth=1
# Install PHP dependencies (production — no dev packages)
composer install --no-dev --optimize-autoloader
# Build frontend assets
npm ci
npm run build
# Set file permissions
sudo chown -R www-data:www-data /var/www/casino
sudo find /var/www/casino -type f -exec chmod 644 {} ;
sudo find /var/www/casino -type d -exec chmod 755 {} ;
sudo chmod -R 775 /var/www/casino/storage
sudo chmod -R 775 /var/www/casino/bootstrap/cache
sudo chmod 600 /var/www/casino/.env
# Configure environment (see Section 10)
cp .env.example .env
# Edit .env with your production values
# Generate application secrets
php artisan key:generate
php artisan jwt:secret
# Run migrations and seed initial data (first deploy only)
php artisan migrate --force
php artisan db:seed --force
# Cache config, routes, views, and events
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
# Create storage symlink
php artisan storage:link
# Publish Horizon assets
php artisan horizon:publish
3. Nginx configuration
Save as /etc/nginx/sites-available/casino:
upstream php-fpm {
server unix:/run/php/php8.3-fpm.sock;
}
# Redirect HTTP → HTTPS
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
# Main HTTPS server block
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
root /var/www/casino/public;
index index.php;
# SSL — managed by Certbot (see Section 9)
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript
text/xml application/xml image/svg+xml;
gzip_min_length 1000;
# Max upload size for KYC documents
client_max_body_size 10M;
# Laravel SPA entry point
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# PHP handling
location ~ .php$ {
fastcgi_pass php-fpm;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_read_timeout 60;
fastcgi_buffer_size 16k;
fastcgi_buffers 4 16k;
}
# WebSocket proxy — Laravel Reverb
location /app/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400;
proxy_send_timeout 86400;
}
# Static asset caching (Vite generates content-hashed filenames)
location /build/ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# Deny access to hidden and sensitive files
location ~ /. { deny all; }
location ~ .(env|log|htaccess)$ { deny all; }
}
Enable the site:
sudo ln -s /etc/nginx/sites-available/casino /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx
4. PHP-FPM tuning
Create a dedicated pool at /etc/php/8.3/fpm/pool.d/casino.conf:
[casino]
user = www-data
group = www-data
listen = /run/php/php8.3-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500
php_admin_value[error_log] = /var/log/php8.3-fpm-casino.log
php_admin_flag[log_errors] = on
php_value[memory_limit] = 256M
php_value[max_execution_time] = 60
php_value[upload_max_filesize] = 10M
php_value[post_max_size] = 12M
; OPcache — critical for production performance
php_admin_value[opcache.enable] = 1
php_admin_value[opcache.memory_consumption] = 256
php_admin_value[opcache.interned_strings_buffer] = 16
php_admin_value[opcache.max_accelerated_files] = 20000
php_admin_value[opcache.revalidate_freq] = 0
php_admin_value[opcache.validate_timestamps] = 0
php_admin_value[opcache.save_comments] = 1
sudo systemctl restart php8.3-fpm
Tuning pm.max_children: Start at 50. Raise to (available RAM - 2 GB) / 30 MB as a rough guide. Monitor with ps aux | grep php-fpm and raise if workers are always at maximum.
5. MySQL tuning
Create /etc/mysql/mysql.conf.d/casino.cnf:
[mysqld]
# InnoDB buffer pool — 50–70% of available RAM
innodb_buffer_pool_size = 4G
innodb_buffer_pool_instances = 4
# Redo log
innodb_log_file_size = 512M
innodb_log_buffer_size = 64M
# ACID compliance — required for financial data
innodb_flush_log_at_trx_commit = 1
innodb_flush_method = O_DIRECT
# Connections
max_connections = 300
wait_timeout = 600
# Binary log for point-in-time recovery
log_bin = /var/log/mysql/mysql-bin.log
expire_logs_days = 7
binlog_format = ROW
Create the database and user:
CREATE DATABASE online_casino CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'casino'@'localhost' IDENTIFIED BY 'strong-password-here';
GRANT ALL PRIVILEGES ON online_casino.* TO 'casino'@'localhost';
FLUSH PRIVILEGES;
sudo systemctl restart mysql
6. Redis configuration
Edit /etc/redis/redis.conf:
# Bind to localhost only — never expose Redis publicly
bind 127.0.0.1
# Strong password
requirepass your-strong-redis-password
# Memory limit — leave 1–2 GB for the OS
maxmemory 2gb
maxmemory-policy allkeys-lru
# RDB persistence snapshots
save 900 1
save 300 10
save 60 10000
# AOF persistence for durability
appendonly yes
appendfsync everysec
# Slow log
slowlog-log-slower-than 10000
slowlog-max-len 128
sudo systemctl restart redis-server
7. Systemd services (Horizon + Reverb)
Two long-running processes must be kept alive by systemd.
Laravel Horizon (queue workers)
Save as /etc/systemd/system/casino-horizon.service:
[Unit]
Description=Online Casino Script — Laravel Horizon
After=network.target mysql.service redis.service
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/var/www/casino
ExecStart=/usr/bin/php artisan horizon
Restart=on-failure
RestartSec=5s
KillSignal=SIGTERM
TimeoutStopSec=60
StandardOutput=journal
StandardError=journal
SyslogIdentifier=casino-horizon
[Install]
WantedBy=multi-user.target
Laravel Reverb (WebSocket server)
Save as /etc/systemd/system/casino-reverb.service:
[Unit]
Description=Online Casino Script — Laravel Reverb WebSocket Server
After=network.target mysql.service redis.service
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/var/www/casino
ExecStart=/usr/bin/php artisan reverb:start --host=127.0.0.1 --port=8080
Restart=on-failure
RestartSec=5s
KillSignal=SIGTERM
TimeoutStopSec=30
StandardOutput=journal
StandardError=journal
SyslogIdentifier=casino-reverb
[Install]
WantedBy=multi-user.target
Enable and start:
sudo systemctl daemon-reload
sudo systemctl enable casino-horizon casino-reverb
sudo systemctl start casino-horizon casino-reverb
# Verify both are running
sudo systemctl status casino-horizon casino-reverb
8. Cron schedule
Add to /etc/cron.d/casino:
# Laravel scheduler — runs every minute
* * * * * www-data cd /var/www/casino && php artisan schedule:run >> /dev/null 2>&1
# Daily database backup at 02:00 UTC
0 2 * * * www-data cd /var/www/casino && bash scripts/backup/backup-database.sh >> /var/log/casino-backup-db.log 2>&1
# Daily Redis backup at 02:30 UTC
30 2 * * * www-data cd /var/www/casino && bash scripts/backup/backup-redis.sh >> /var/log/casino-backup-redis.log 2>&1
The Laravel scheduler manages:
- 03:00 UTC — Daily ledger balance audit (
LEDGER_AUDIT_TIME) - 04:00 UTC — Multi-account detection scan (
MULTI_ACCOUNT_DETECTION_TIME) - 05:00 UTC — Bonus abuse detection (
BONUS_ABUSE_DETECTION_TIME) - 06:00 UTC — Betting velocity check (
VELOCITY_CHECK_TIME) - Every 5 minutes — Horizon queue metrics snapshot
Adjust these times in .env to avoid peak traffic hours for your timezone.
9. SSL certificate
# Obtain a Let's Encrypt certificate (Certbot auto-configures Nginx)
sudo certbot --nginx -d example.com -d www.example.com
# Verify auto-renewal
sudo certbot renew --dry-run
sudo systemctl status certbot.timer
Certbot installs a systemd timer that renews certificates automatically 30 days before expiry.
10. Environment variables for production
A minimal production-ready .env:
APP_NAME="Your Casino"
APP_ENV=production
APP_KEY= # php artisan key:generate
APP_DEBUG=false # MUST be false in production
APP_URL=https://example.com
FORCE_HTTPS=true
CSP_ENABLED=true
CORS_ALLOWED_ORIGINS=https://example.com
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_DATABASE=online_casino
DB_USERNAME=casino
DB_PASSWORD=
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=
REDIS_PORT=6379
REDIS_CACHE_DB=1
REDIS_SESSION_DB=2
SESSION_DRIVER=redis
SESSION_SECURE_COOKIE=true
SESSION_SAME_SITE=strict
JWT_SECRET= # php artisan jwt:secret
REVERB_APP_ID=online-casino
REVERB_APP_KEY= # openssl rand -hex 16
REVERB_APP_SECRET= # openssl rand -hex 32
REVERB_HOST=example.com
REVERB_PORT=443
REVERB_SCHEME=https
REVERB_ALLOWED_ORIGINS=https://example.com
VITE_REVERB_APP_KEY= # same as REVERB_APP_KEY
VITE_REVERB_HOST=example.com
VITE_REVERB_PORT=443
VITE_REVERB_SCHEME=https
MAIL_MAILER=smtp
MAIL_HOST=
MAIL_PORT=587
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=noreply@example.com
MAIL_FROM_NAME="Your Casino"
LOG_LEVEL=warning
CACHE_STORE=redis
QUEUE_CONNECTION=redis
After filling in values:
php artisan config:cache
php artisan route:cache
php artisan view:cache
Important: After any
.envchange in production, always runphp artisan config:clear && php artisan config:cacheto apply the change.
For the full reference of all available variables, see the Configuration Reference.
11. Health and monitoring
Health endpoint
curl https://example.com/api/health
Expected response:
{
"status": "ok",
"database": "ok",
"redis": "ok",
"horizon": "ok",
"timestamp": "2026-01-01T00:00:00Z"
}
Configure your uptime monitor (UptimeRobot, Pingdom, Better Stack) to alert on any non-200 response or a non-"ok" status field.
Horizon dashboard
Access at https://example.com/horizon (requires admin login).
Monitor:
- Queue throughput and wait times
- Failed job count — alert if non-zero for more than a few minutes
- Worker process count
Log locations
| Log | Path |
|---|---|
| Application | storage/logs/laravel.log |
| Structured JSON | storage/logs/structured.json |
| Nginx access | /var/log/nginx/access.log |
| Nginx errors | /var/log/nginx/error.log |
| PHP-FPM | /var/log/php8.3-fpm-casino.log |
| Horizon | journalctl -u casino-horizon -f |
| Reverb | journalctl -u casino-reverb -f |
Alerting
Configure the following in .env to receive critical alerts:
ALERT_SLACK_WEBHOOK=https://hooks.slack.com/services/...
ALERT_EMAIL_TO=ops@example.com
Alerts fire for: failed queue jobs, health endpoint failures, AML rule triggers, and high error rates.
12. Log management
Add a logrotate configuration at /etc/logrotate.d/casino:
/var/www/casino/storage/logs/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0664 www-data www-data
sharedscripts
postrotate
kill -USR1 $(cat /run/php/php8.3-fpm.pid) 2>/dev/null || true
endscript
}
This keeps 14 days of compressed logs and prevents unbounded disk growth.
13. Backup strategy
Database
Run scripts/backup/backup-database.sh (included in the repository) daily via cron. It creates a timestamped compressed MySQL dump.
# Manual backup
bash scripts/backup/backup-database.sh
Redis
Run scripts/backup/backup-redis.sh daily. It copies the Redis RDB snapshot.
Restoring
# Restore MySQL
mysql -u casino -p online_casino < /path/to/backup.sql
# Restore Redis
sudo systemctl stop redis-server
cp /path/to/dump.rdb /var/lib/redis/dump.rdb
sudo chown redis:redis /var/lib/redis/dump.rdb
sudo systemctl start redis-server
Store backups off-server — upload to S3, Backblaze B2, or equivalent. Retain at least 30 days. Test your restore procedure before going live.
14. Zero-downtime deploys
cd /var/www/casino
git pull
composer install --no-dev --optimize-autoloader
npm ci && npm run build
php artisan migrate --force
php artisan config:cache && php artisan route:cache && php artisan view:cache && php artisan event:cache
php artisan horizon:terminate # Horizon drains the queue and restarts via systemd
sudo systemctl restart php8.3-fpm
PHP-FPM restarts gracefully — in-flight requests complete before workers are replaced. Horizon terminates after draining its current jobs (systemd restarts it automatically). No downtime for players.
15. Horizontal scaling
When to scale
| Concurrent players | Setup |
|---|---|
| < 500 | 1 app server, 1 MySQL, 1 Redis |
| 500–2,000 | 2 app servers, 1 MySQL + 1 replica, 1 Redis |
| 2,000–10,000 | 3–5 app servers, MySQL + 2 replicas, Redis Cluster, 2 Reverb nodes |
| > 10,000 | Autoscaling group, MySQL Galera / RDS Multi-AZ, Redis Sentinel |
Architecture
Players → Load Balancer → App Server 1 ─┐
→ App Server N ─┤── Shared Redis
└── MySQL Primary ← MySQL Replica(s)
└── Reverb Server(s)
Required .env changes for multi-server
# Read replicas (comma-separated)
DB_READ_HOST=replica1.example.com,replica2.example.com
# Redis pub/sub for WebSocket scaling
REVERB_SCALING_ENABLED=true
# Session must be Redis-backed on all servers
SESSION_DRIVER=redis
# File storage must be shared (S3 for multi-server)
FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=eu-west-1
AWS_BUCKET=your-bucket
# Trust the load balancer's IP
APP_TRUSTED_PROXIES=10.0.0.0/8
Per-server deployment steps
Run on each app server:
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan storage:link
Run on one designated server only:
php artisan migrate --force
Load balancer configuration
- Use round-robin (the API is stateless — no sticky sessions required)
- Health check target:
GET /api/health— expect HTTP 200 - Forward
X-Real-IP,X-Forwarded-For,X-Forwarded-Protoheaders - Terminate TLS at the load balancer
16. Security checklist
- [ ]
APP_DEBUG=falsein production - [ ]
APP_KEYis 32 characters, randomly generated - [ ]
JWT_SECRETis set and unique per installation - [ ]
REVERB_APP_KEYandREVERB_APP_SECRETare random and unique - [ ] Redis has a strong password (
requirepassinredis.conf) - [ ] Redis is bound to
127.0.0.1— never publicly accessible - [ ] MySQL user has only the required privileges (no
SUPER, noFILE) - [ ] Firewall: only ports 80, 443, and 22 (SSH) are publicly accessible
- [ ] SSL certificate is valid and auto-renews
- [ ]
SESSION_SECURE_COOKIE=true(requires HTTPS) - [ ]
FORCE_HTTPS=trueset - [ ]
CSP_ENABLED=true(Content-Security-Policy header active) - [ ]
.envpermissions:chmod 600 .env - [ ]
php artisan config:cacherun after each.envchange - [ ] SSH key-only authentication (password login disabled)
- [ ] ClamAV enabled for KYC file uploads (
CLAMAV_ENABLED=true) if required - [ ] Fail2ban protecting SSH and Nginx login endpoints
17. Pre-launch checklist
Infrastructure
- [ ] DNS records pointing to server IP
- [ ] SSL certificate issued and valid —
https://example.comloads without browser warning - [ ] Both systemd services running:
sudo systemctl status casino-horizon casino-reverb - [ ] Cron running:
systemctl status cron
Application
- [ ] All migrations applied:
php artisan migrate:status(no pending) - [ ] Database seeded with initial data (admin, games, payment methods)
- [ ] Config cached:
php artisan config:cache - [ ] Routes cached:
php artisan route:cache - [ ] Storage symlink created:
ls -la public/storage - [ ] Frontend assets built:
ls public/build/manifest.json
Configuration
- [ ] Admin password changed (not
password) - [ ] 2FA enabled on all admin accounts
- [ ] At least one payment method enabled and tested
- [ ] At least one game enabled
- [ ] SMTP email tested — test email sent from Admin → Settings → Email
- [ ] Site name, currency, and timezone set in Admin → Settings → General
- [ ] Legal pages reviewed and customised for your jurisdiction
Monitoring
- [ ] Health endpoint returns 200:
curl https://example.com/api/health - [ ] Horizon dashboard accessible and showing workers:
https://example.com/horizon - [ ] Log rotation configured
- [ ] Backup scripts tested with a successful restore verification
- [ ] Alerting configured (
ALERT_SLACK_WEBHOOKorALERT_EMAIL_TO) - [ ] Uptime monitor configured (external check of
/api/health)
Security
- [ ] All items in Section 16 checked
- [ ] Responsible gaming features enabled: deposit limits, self-exclusion, session reminders
- [ ] Penetration test completed (recommended before accepting real players)
For the initial install, see Installation Guide. For all .env variables, see Configuration Reference. For API integration, see API Reference.