Common Setup
Server configuration for FieldOps: web server, HTTPS, background processes, mail, storage, backups and every .env key.
Where Settings Live
Placeholders on this page: app.example.com stands for your domain, /var/www/fieldops for the folder where you installed FieldOps and www-data for your web-server user. Replace them with your own values. Paths that do not start with /, such as deploy/nginx/fieldops.conf, are relative to that installation folder.
Most settings are made in the browser and stored in the database (credentials encrypted with APP_KEY). .env holds only what the server needs before the app can read its database, plus platform-wide fallbacks.
| Setting | Where |
|---|---|
| Branding, website design, panel theme, maintenance mode, reCAPTCHA, SEO, multi-vendor switch, plans, platform gateways, languages, backups schedule | Platform console (/admin) |
| Company details, customer gateways, SMS/WhatsApp/push credentials, Claude key, website, booking, webhooks | Company workspace (/company) |
| App URL, database, cache/queue/session drivers, mail, storage, Reverb, platform fallbacks for messaging and AI, OSRM | .env (.env Reference) |
The installer creates .env from .env.example and writes the production values. After editing .env run php artisan config:cache and php artisan queue:restart.
Nginx & Apache
Nginx: deploy/nginx/fieldops.conf is a complete site: HTTP-to-HTTPS redirect, PHP-FPM 8.3, client_max_body_size 64M, security headers (HSTS, nosniff, X-Frame-Options except on /book/ so the booking widget can be embedded, Referrer-Policy, Permissions-Policy), long caching for /build/, and no access to dot-files or other PHP files.
sudo cp deploy/nginx/fieldops.conf /etc/nginx/sites-available/fieldops # set server_name, root and certificate paths sudo ln -s /etc/nginx/sites-available/fieldops /etc/nginx/sites-enabled/ sudo nginx -t && sudo systemctl reload nginx
Apache: enable mod_rewrite and let public/.htaccess work (AllowOverride All):
<VirtualHost *:443>
ServerName app.example.com
DocumentRoot /var/www/fieldops/public
<Directory /var/www/fieldops/public>
AllowOverride All
Require all granted
</Directory>
# SSLEngine on + certificate lines (certbot --apache adds them)
</VirtualHost>PHP settings for both: deploy/php/99-fieldops.ini (uploads 64M, memory_limit 256M, max_execution_time 120, OPcache with validate_timestamps=1 because System Update copies PHP files at runtime).
HTTPS & Proxies
Use a valid certificate (VPS guide or cPanel guide), set APP_URL=https://… and SESSION_SECURE_COOKIE=true. Payment gateways, the app and signed links all use APP_URL.
Behind a load balancer or Cloudflare: the app does not trust proxy headers by default. Add $middleware->trustProxies(at: '*') (or your proxy’s IP range) inside withMiddleware in bootstrap/app.php, otherwise links and gateway callbacks are generated with http:// and client IPs are wrong. Do not cache or challenge /payments/*, /api/* and /broadcasting/auth.
Permissions & Storage Link
cd /var/www/fieldops sudo chown -R www-data:www-data . sudo chmod 640 .env sudo -u www-data php artisan storage:link # public/storage -> storage/app/public (logos, photos)
The web server writes to storage/ and bootstrap/cache, to .env during installation, and to the project folders when you apply an in-app System update, so the folder belongs to the web-server user. Public uploads (logos, photos) live in storage/app/public and are served through the public/storage link; private files (receipts, payment proofs, backups) stay in storage/app/private and storage/app/backups. Never use 777.
Cron & Scheduler
One entry, every minute, as the web-server user:
# /etc/cron.d/fieldops (shipped as deploy/cron/fieldops) * * * * * www-data cd /var/www/fieldops && php artisan schedule:run >> /dev/null 2>&1
Or run php artisan schedule:work under Supervisor (deploy/supervisor/fieldops-scheduler.conf); use one of the two, not both. The full task list is in Web Platform → Scheduler. Times use the app time zone (UTC by default), not the server’s. System → Platform health shows the last heartbeat; php artisan schedule:list shows the next runs.
Queue Worker
deploy/supervisor/fieldops-worker.conf runs two workers. notifications comes first so alerts are not stuck behind slow jobs; webhooks must be in the list for outgoing webhooks.
[program:fieldops-worker] process_name=%(program_name)s_%(process_num)02d command=php /var/www/fieldops/artisan queue:work --queue=notifications,webhooks,default --sleep=3 --tries=3 --backoff=10 --timeout=75 --max-time=3600 --max-jobs=1000 --memory=256 directory=/var/www/fieldops user=www-data numprocs=2 autostart=true autorestart=true stopasgroup=true killasgroup=true stopwaitsecs=90 redirect_stderr=true stdout_logfile=/var/www/fieldops/storage/logs/worker.log
sudo cp deploy/supervisor/fieldops-worker.conf /etc/supervisor/conf.d/ sudo supervisorctl reread && sudo supervisorctl update && sudo supervisorctl start 'fieldops-worker:*'
After every deploy or .env change run php artisan queue:restart. Failed jobs: php artisan queue:failed, php artisan queue:retry all. On shared hosting without Supervisor, use the once-per-minute queue:work --stop-when-empty cron job from cPanel Install.
Reverb (Live Chat)
Optional: live chat works by polling without it. For instant messages:
Keys in
.envBROADCAST_CONNECTION=reverb REVERB_APP_ID=... # php artisan reverb:install generates the three values REVERB_APP_KEY=... REVERB_APP_SECRET=... REVERB_HOST=app.example.com # public address browsers and the app server connect to REVERB_PORT=443 REVERB_SCHEME=https REVERB_SERVER_HOST=127.0.0.1 # where reverb:start listens (behind nginx) REVERB_SERVER_PORT=8080
Proxy WebSockets through nginx
# inside the server { listen 443 ... } block of deploy/nginx/fieldops.conf location ~ ^/apps?/ { proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 60s; proxy_pass http://127.0.0.1:8080; }Keep it running
[program:fieldops-reverb] command=php /var/www/fieldops/artisan reverb:start --host=127.0.0.1 --port=8080 directory=/var/www/fieldops user=www-data autostart=true autorestart=true redirect_stderr=true stdout_logfile=/var/www/fieldops/storage/logs/reverb.log
Save as
/etc/supervisor/conf.d/fieldops-reverb.conf, thensudo supervisorctl reread && sudo supervisorctl update.Rebuild and cache
npm ci && npm run build(the workspace inbox readsVITE_REVERB_*at build time), thenphp artisan config:cache.Check
Platform health shows “Reverb is reachable”; a message from the website widget appears in Live chat instantly.
Port 8080 is only the internal port. If something else on the server already uses it, pick another REVERB_SERVER_PORT and use the same port in proxy_pass and the Supervisor command.
Cache, Sessions & Redis
database drivers for cache, sessions and queue work everywhere and need nothing extra (the tables exist). On a VPS, Redis is faster: install redis-server and php8.3-redis, set a requirepass, then CACHE_STORE=redis, SESSION_DRIVER=redis, QUEUE_CONNECTION=redis, REDIS_PASSWORD, REDIS_QUEUE_RETRY_AFTER=90. fieldops:check-production verifies the extension.
Enter your SMTP details in System → Platform settings; they apply at once and the password is stored encrypted. Until then the app uses the MAIL_* values in .env as a fallback:
MAIL_MAILER=smtp MAIL_SCHEME=null # null = STARTTLS on 587; smtps for implicit TLS on 465 MAIL_HOST=smtp.your-provider.com MAIL_PORT=587 MAIL_USERNAME=... MAIL_PASSWORD=... MAIL_FROM_ADDRESS="[email protected]" MAIL_FROM_NAME="${APP_NAME}"
SMTP, sendmail and log work out of the box. Amazon SES, Postmark, Mailgun or Resend need their mailer package first (for example composer require symfony/postmark-mailer symfony/http-client) and their API key (POSTMARK_API_KEY, RESEND_API_KEY, AWS_*). Set SPF, DKIM and DMARC for the MAIL_FROM_ADDRESS domain, then trigger a password reset to an outside inbox.
S3-Compatible Storage
With FILESYSTEM_DISK=local files stay on this server (fine for one server; include them in backups). For S3, DigitalOcean Spaces, Cloudflare R2, Wasabi or MinIO, install the adapter (not included by default) and set the keys:
composer require league/flysystem-aws-s3-v3 "^3.0" # .env FILESYSTEM_DISK=s3 AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... AWS_DEFAULT_REGION=... AWS_BUCKET=... AWS_ENDPOINT= # DigitalOcean Spaces, Cloudflare R2, Wasabi, MinIO AWS_USE_PATH_STYLE_ENDPOINT=false # true for MinIO # copy existing files: aws s3 sync storage/app/ s3://<bucket>/ --exclude "backups/*"
Use a private bucket and a key limited to it (s3:GetObject, PutObject, DeleteObject, ListBucket). Backups stay on local disk in storage/app/backups either way.
PDFs & Fonts
PDF_ENGINE=auto renders Bangla documents with mPDF (with the bundled Noto Sans Bengali in resources/fonts) and everything else with dompdf. Set PDF_BANGLA_FONT_PATH only to use another TTF with Bengali glyphs; PDF_TEMP_DIR must be writable (default storage/framework/cache/mpdf).
Backups
Turn on the daily backup in System → Backups (02:00, with retention). Copy the files off the server:
# nightly copy of the database backups off the server (example with rclone or the AWS CLI) 30 3 * * * www-data rclone sync /var/www/fieldops/storage/app/backups remote:fieldops-backups/db # or: aws s3 sync /var/www/fieldops/storage/app/backups s3://<backup-bucket>/db/
Also back up .env (with APP_KEY) and, with local storage, storage/app/public and storage/app/private. Test a restore into a scratch database: mysql scratch_db < storage/app/backups/<file>.sql.
Monitoring & Logs
storage/logs/laravel.log(orlaravel-YYYY-MM-DD.logwithLOG_STACK=daily);worker.log,scheduler.log,reverb.logfrom Supervisor.- System → Platform health: database, cache, queue, scheduler, storage and disk use per company, Reverb, mail, recent errors.
GET /upanswers 200 for an uptime monitor. php artisan fieldops:check-production --jsonfor scripts and CI.- Optional error alerts to Slack (
LOG_SLACK_WEBHOOK_URL, addslacktoLOG_STACK).
.env Reference
Every key below is read by the app (.env.example and config/*.php). Most third-party credentials can also be set in the browser; the .env values are then only the platform-wide fallback.
Application
| Key | Meaning |
|---|---|
APP_NAME | Platform name in e-mails and titles (the installer writes your site name) |
APP_ENV / APP_DEBUG | production / false on a live site |
APP_KEY | Generated once; encrypts stored gateway and channel credentials. Never change it on a running site |
APP_URL | Public https:// address; used for payment callbacks, signed links and e-mails |
APP_INSTALLED | true after installing; false/empty opens the installer |
APP_DEMO | true only on a public demo (blocks dangerous actions, schedules the demo reset) |
APP_LOCALE, APP_FALLBACK_LOCALE | Default language codes (en) |
FIELDOPS_VERSION | Base version shown in System update (1.0.0) |
FIELDOPS_DEFAULT_TENANT_ID | Fallback main company id; leave empty |
API_RATE_LIMIT | Company/mobile API requests per minute per user (120) |
RECAPTCHA_FORCE_DISABLE | Emergency switch when reCAPTCHA locks everyone out |
Installer / licence
| Key | Meaning |
|---|---|
ENVATO_VERIFY_URL | License server for the purchase-code check |
ENVATO_PERSONAL_TOKEN, ENVATO_ITEM_ID | Envato API check instead of a license server; neither = format check only |
Database, cache, session, queue
| Key | Meaning |
|---|---|
DB_CONNECTION, DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD | MySQL connection (the installer writes them); DB_CHARSET/DB_COLLATION utf8mb4/utf8mb4_unicode_ci |
CACHE_STORE, SESSION_DRIVER, QUEUE_CONNECTION | database (no extra software) or redis; never sync for the queue |
REDIS_CLIENT, REDIS_HOST, REDIS_PASSWORD, REDIS_PORT | Redis (needs the phpredis extension) |
SESSION_LIFETIME, SESSION_ENCRYPT, SESSION_SECURE_COOKIE, SESSION_SAME_SITE | SESSION_SECURE_COOKIE=true on HTTPS; keep SESSION_SAME_SITE=lax (gateway returns are cross-site) |
DB_QUEUE_RETRY_AFTER / REDIS_QUEUE_RETRY_AFTER | 90; must be larger than the worker --timeout (75) |
SANCTUM_STATEFUL_DOMAINS | Your domain |
Logging
| Key | Meaning |
|---|---|
LOG_CHANNEL, LOG_STACK, LOG_LEVEL | Production: LOG_STACK=daily, LOG_LEVEL=warning; LOG_DAILY_DAYS keeps 14 days |
| Key | Meaning |
|---|---|
MAIL_MAILER, MAIL_SCHEME, MAIL_HOST, MAIL_PORT, MAIL_USERNAME, MAIL_PASSWORD, MAIL_FROM_ADDRESS, MAIL_FROM_NAME | What the app really sends with (Mail) |
Storage
| Key | Meaning |
|---|---|
FILESYSTEM_DISK | local (default) or s3 |
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION, AWS_BUCKET, AWS_URL, AWS_ENDPOINT, AWS_USE_PATH_STYLE_ENDPOINT | S3 or S3-compatible storage (Storage) |
Payments
| Key | Meaning |
|---|---|
PAYMENTS_DEFAULT_CURRENCY, PAYMENTS_HTTP_TIMEOUT, PAYMENTS_PAY_LINK_DAYS | USD, 20 s, 30 days |
PLATFORM_SSLCOMMERZ_*, PLATFORM_STRIPE_*, PLATFORM_PAYPAL_*, PLATFORM_RAZORPAY_*, PLATFORM_PAYSTACK_*, PLATFORM_FLUTTERWAVE_* | Fallback credentials for vendor subscription payments (the console screen is preferred) |
SSLCOMMERZ_SANDBOX_URL, SSLCOMMERZ_LIVE_URL, BKASH_SANDBOX_URL, BKASH_LIVE_URL, STRIPE_API_URL, STRIPE_WEBHOOK_TOLERANCE | Gateway endpoints; defaults are correct |
BANK_TRANSFER_PROOF_MAX_KB | Bank transfer receipt size (5120) |
Messaging
| Key | Meaning |
|---|---|
SMS_DRIVER, SMS_GATEWAY_URL, SMS_GATEWAY_METHOD, SMS_GATEWAY_PARAMS, SMS_GATEWAY_API_KEY, SMS_SENDER_ID | Generic HTTP SMS gateway |
WHATSAPP_DRIVER, WHATSAPP_PHONE_NUMBER_ID, WHATSAPP_ACCESS_TOKEN, WHATSAPP_API_VERSION, WHATSAPP_BASE_URL | WhatsApp Cloud API |
FCM_DRIVER, FCM_PROJECT_ID, FCM_SERVICE_ACCOUNT_PATH, FCM_SERVICE_ACCOUNT_JSON | Push to the app |
FEEDBACK_LINK_DAYS | Validity of feedback links (30) |
AI
| Key | Meaning |
|---|---|
ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL | Platform Claude key (optional) |
AI_DEFAULT_MODEL, AI_PLATFORM_KEY_PLANS, AI_PLATFORM_MONTHLY_TOKENS, AI_RATE_LIMIT_PER_MINUTE, AI_TIMEOUT, AI_REFUSAL_FALLBACK | Model and limits (AI Assistant) |
Maps, routing, tracking
| Key | Meaning |
|---|---|
ROUTING_DRIVER, OSRM_URL, OSRM_PROFILE, OSRM_TIMEOUT, ROUTING_ROAD_FACTOR, ROUTING_AVERAGE_SPEED_KMH | Route optimisation |
GEOCODING_SEARCH_URL (default https://photon.komoot.io), GEOCODING_URL (default https://nominatim.openstreetmap.org) | Booking page location search and reverse geocoding; point at your own servers for heavy use |
TRACKING_PING_INTERVAL_SECONDS, TRACKING_LINK_HOURS, TRACKING_PING_RETENTION_DAYS, TRACKING_STALE_MINUTES, TRACKING_POLL_SECONDS | Live tracking |
SLA_ESCALATION_LOOKBACK_MINUTES, FIELD_OTP_MAX_ATTEMPTS | SLA escalation window; tries for job verification codes |
Webhooks
| Key | Meaning |
|---|---|
WEBHOOK_QUEUE, WEBHOOK_MAX_ATTEMPTS, WEBHOOK_DISABLE_AFTER_FAILURES, WEBHOOK_TIMEOUT, WEBHOOK_CONNECT_TIMEOUT, WEBHOOK_ALLOW_PRIVATE_TARGETS | Outgoing webhooks (webhooks, 5, 15, 10, 5, unset) |
| Key | Meaning |
|---|---|
PDF_ENGINE | auto (mPDF for Bangla, dompdf otherwise), mpdf or dompdf |
PDF_BANGLA_FONT_PATH, PDF_TEMP_DIR | Empty = the bundled Noto Sans Bengali; mPDF temp folder |
Real-time (Reverb)
| Key | Meaning |
|---|---|
BROADCAST_CONNECTION | log (polling) or reverb |
REVERB_APP_ID, REVERB_APP_KEY, REVERB_APP_SECRET, REVERB_HOST, REVERB_PORT, REVERB_SCHEME, REVERB_SERVER_HOST, REVERB_SERVER_PORT | Reverb (Reverb) |
VITE_REVERB_APP_KEY, VITE_REVERB_HOST, VITE_REVERB_PORT, VITE_REVERB_SCHEME | Copied from the REVERB_* values at build time (npm run build) |
Updates and demo
| Key | Meaning |
|---|---|
FIELDOPS_UPDATE_MAX_KB | Largest update zip (51200) |
FIELDOPS_UPDATE_URL, FIELDOPS_LICENSE_KEY, FIELDOPS_UPDATE_TIMEOUT, FIELDOPS_UPDATE_CACHE_HOURS | Remote update check |
FIELDOPS_DEMO_RESET_CRON, FIELDOPS_DEMO_PASSWORD | Demo installs only |
Check It Works
php artisan fieldops:check-production # 0 critical? php artisan schedule:list # every task listed sudo supervisorctl status # fieldops-worker:* (and fieldops-reverb) RUNNING php artisan queue:failed # empty curl -I https://app.example.com # 200/302, Strict-Transport-Security header present
/installanswers with your site (not the installer) after installing.- Sign-in, workspace and console load over HTTPS without mixed-content warnings.
- A logo uploaded in Platform settings shows on the website (storage link).
- Platform health is green for database, queue and scheduler.
- A test e-mail and, if used, a test SMS / WhatsApp / push arrive.
- A sandbox payment marks an invoice paid; the gateway’s webhook log shows
200.
Third-Party Costs
Hosting, domain and an e-mail service are required; payment gateways, SMS, WhatsApp, Firebase, Anthropic, OSRM and the app store accounts are optional. The full table with who pays is in Web Platform → Third-Party Services & Costs.