Version 1.0.0Web + App

Common Setup

Server configuration for FieldOps: web server, HTTPS, background processes, mail, storage, backups and every .env key.

Item: FieldOps | Multi-Vendor Service Management Platform With Flutter Mobile App  ·  Stack: Laravel 13 · PHP 8.3+ · MySQL 8 · Flutter 3.47 (Dart 3.13)

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.

SettingWhere
Branding, website design, panel theme, maintenance mode, reCAPTCHA, SEO, multi-vendor switch, plans, platform gateways, languages, backups schedulePlatform console (/admin)
Company details, customer gateways, SMS/WhatsApp/push credentials, Claude key, website, booking, webhooksCompany 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:

  1. Keys in .env
    BROADCAST_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
  2. 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;
    }
  3. 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, then sudo supervisorctl reread && sudo supervisorctl update.

  4. Rebuild and cache
    npm ci && npm run build (the workspace inbox reads VITE_REVERB_* at build time), then php artisan config:cache.
  5. 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.

Mail

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 (or laravel-YYYY-MM-DD.log with LOG_STACK=daily); worker.log, scheduler.log, reverb.log from Supervisor.
  • System → Platform health: database, cache, queue, scheduler, storage and disk use per company, Reverb, mail, recent errors. GET /up answers 200 for an uptime monitor.
  • php artisan fieldops:check-production --json for scripts and CI.
  • Optional error alerts to Slack (LOG_SLACK_WEBHOOK_URL, add slack to LOG_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
KeyMeaning
APP_NAMEPlatform name in e-mails and titles (the installer writes your site name)
APP_ENV / APP_DEBUGproduction / false on a live site
APP_KEYGenerated once; encrypts stored gateway and channel credentials. Never change it on a running site
APP_URLPublic https:// address; used for payment callbacks, signed links and e-mails
APP_INSTALLEDtrue after installing; false/empty opens the installer
APP_DEMOtrue only on a public demo (blocks dangerous actions, schedules the demo reset)
APP_LOCALE, APP_FALLBACK_LOCALEDefault language codes (en)
FIELDOPS_VERSIONBase version shown in System update (1.0.0)
FIELDOPS_DEFAULT_TENANT_IDFallback main company id; leave empty
API_RATE_LIMITCompany/mobile API requests per minute per user (120)
RECAPTCHA_FORCE_DISABLEEmergency switch when reCAPTCHA locks everyone out
Installer / licence
KeyMeaning
ENVATO_VERIFY_URLLicense server for the purchase-code check
ENVATO_PERSONAL_TOKEN, ENVATO_ITEM_IDEnvato API check instead of a license server; neither = format check only
Database, cache, session, queue
KeyMeaning
DB_CONNECTION, DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORDMySQL connection (the installer writes them); DB_CHARSET/DB_COLLATION utf8mb4/utf8mb4_unicode_ci
CACHE_STORE, SESSION_DRIVER, QUEUE_CONNECTIONdatabase (no extra software) or redis; never sync for the queue
REDIS_CLIENT, REDIS_HOST, REDIS_PASSWORD, REDIS_PORTRedis (needs the phpredis extension)
SESSION_LIFETIME, SESSION_ENCRYPT, SESSION_SECURE_COOKIE, SESSION_SAME_SITESESSION_SECURE_COOKIE=true on HTTPS; keep SESSION_SAME_SITE=lax (gateway returns are cross-site)
DB_QUEUE_RETRY_AFTER / REDIS_QUEUE_RETRY_AFTER90; must be larger than the worker --timeout (75)
SANCTUM_STATEFUL_DOMAINSYour domain
Logging
KeyMeaning
LOG_CHANNEL, LOG_STACK, LOG_LEVELProduction: LOG_STACK=daily, LOG_LEVEL=warning; LOG_DAILY_DAYS keeps 14 days
Mail
KeyMeaning
MAIL_MAILER, MAIL_SCHEME, MAIL_HOST, MAIL_PORT, MAIL_USERNAME, MAIL_PASSWORD, MAIL_FROM_ADDRESS, MAIL_FROM_NAMEWhat the app really sends with (Mail)
Storage
KeyMeaning
FILESYSTEM_DISKlocal (default) or s3
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION, AWS_BUCKET, AWS_URL, AWS_ENDPOINT, AWS_USE_PATH_STYLE_ENDPOINTS3 or S3-compatible storage (Storage)
Payments
KeyMeaning
PAYMENTS_DEFAULT_CURRENCY, PAYMENTS_HTTP_TIMEOUT, PAYMENTS_PAY_LINK_DAYSUSD, 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_TOLERANCEGateway endpoints; defaults are correct
BANK_TRANSFER_PROOF_MAX_KBBank transfer receipt size (5120)
Messaging
KeyMeaning
SMS_DRIVER, SMS_GATEWAY_URL, SMS_GATEWAY_METHOD, SMS_GATEWAY_PARAMS, SMS_GATEWAY_API_KEY, SMS_SENDER_IDGeneric HTTP SMS gateway
WHATSAPP_DRIVER, WHATSAPP_PHONE_NUMBER_ID, WHATSAPP_ACCESS_TOKEN, WHATSAPP_API_VERSION, WHATSAPP_BASE_URLWhatsApp Cloud API
FCM_DRIVER, FCM_PROJECT_ID, FCM_SERVICE_ACCOUNT_PATH, FCM_SERVICE_ACCOUNT_JSONPush to the app
FEEDBACK_LINK_DAYSValidity of feedback links (30)
AI
KeyMeaning
ANTHROPIC_API_KEY, ANTHROPIC_BASE_URLPlatform Claude key (optional)
AI_DEFAULT_MODEL, AI_PLATFORM_KEY_PLANS, AI_PLATFORM_MONTHLY_TOKENS, AI_RATE_LIMIT_PER_MINUTE, AI_TIMEOUT, AI_REFUSAL_FALLBACKModel and limits (AI Assistant)
Maps, routing, tracking
KeyMeaning
ROUTING_DRIVER, OSRM_URL, OSRM_PROFILE, OSRM_TIMEOUT, ROUTING_ROAD_FACTOR, ROUTING_AVERAGE_SPEED_KMHRoute 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_SECONDSLive tracking
SLA_ESCALATION_LOOKBACK_MINUTES, FIELD_OTP_MAX_ATTEMPTSSLA escalation window; tries for job verification codes
Webhooks
KeyMeaning
WEBHOOK_QUEUE, WEBHOOK_MAX_ATTEMPTS, WEBHOOK_DISABLE_AFTER_FAILURES, WEBHOOK_TIMEOUT, WEBHOOK_CONNECT_TIMEOUT, WEBHOOK_ALLOW_PRIVATE_TARGETSOutgoing webhooks (webhooks, 5, 15, 10, 5, unset)
PDF
KeyMeaning
PDF_ENGINEauto (mPDF for Bangla, dompdf otherwise), mpdf or dompdf
PDF_BANGLA_FONT_PATH, PDF_TEMP_DIREmpty = the bundled Noto Sans Bengali; mPDF temp folder
Real-time (Reverb)
KeyMeaning
BROADCAST_CONNECTIONlog (polling) or reverb
REVERB_APP_ID, REVERB_APP_KEY, REVERB_APP_SECRET, REVERB_HOST, REVERB_PORT, REVERB_SCHEME, REVERB_SERVER_HOST, REVERB_SERVER_PORTReverb (Reverb)
VITE_REVERB_APP_KEY, VITE_REVERB_HOST, VITE_REVERB_PORT, VITE_REVERB_SCHEMECopied from the REVERB_* values at build time (npm run build)
Updates and demo
KeyMeaning
FIELDOPS_UPDATE_MAX_KBLargest update zip (51200)
FIELDOPS_UPDATE_URL, FIELDOPS_LICENSE_KEY, FIELDOPS_UPDATE_TIMEOUT, FIELDOPS_UPDATE_CACHE_HOURSRemote update check
FIELDOPS_DEMO_RESET_CRON, FIELDOPS_DEMO_PASSWORDDemo 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
  • /install answers 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.