Every Formbricks web process starts one worker with a concurrency of one by default. You can tune these
values with environment variables after you have measured your workload.
Runtime architecture
The web application creates jobs and consumes them from a shared Redis-backed queue:- Queue name:
background-jobs - Redis key prefix:
{formbricks:jobs} - Worker startup: Next.js server instrumentation
- Default workers per web process:
1 - Default concurrent jobs per worker:
1
Changing a recurring job’s cron pattern, interval or time zone — for example by setting
SURVEY_SCHEDULING_TIME_ZONE — updates the schedule as soon as the process restarts, but takes effect from
the following run: the run already queued keeps its previous timing. For the daily sweeps that can be up
to 24 hours. Inspecting the schedule in Redis shows the new value immediately, so a job firing once more at
the old hour after a restart is expected rather than a sign the setting was ignored.Current background jobs
Current workloads on the shared queue include:
These are current examples rather than a fixed list. Future Formbricks features can use the same Job Runner for
additional background work.
Configure the Job Runner
1
Connect Redis
Set All replicas must use the same Redis instance so they share the BullMQ queue and recurring schedules.
REDIS_URL to a Redis instance reachable by every Formbricks web replica.2
Keep the in-process worker enabled
BULLMQ_WORKER_ENABLED is enabled by default outside test environments. You can omit it or set it
explicitly:3
Verify startup
Check the web process logs for the exact message
BullMQ runtime started. Its structured fields include
the queue name, Redis prefix, worker count, and worker concurrency.Scale across replicas
Each web replica starts its own in-process workers and connects to the same queue. BullMQ coordinates job claims so one available worker ordinarily processes a queued job. Total processing capacity is approximately:WEBHOOK_DELIVERY_TIMEOUT_MS (5 seconds by default). Because those
jobs share the queue’s concurrency, the deliveries for one response run as concurrency allows rather than
all at once: at the default of one, ten webhooks are delivered one after another, so the last receiver is
contacted later than it would have been when the pipeline job posted to all of them itself.
Raise BULLMQ_WORKER_CONCURRENCY if your workspaces have several webhooks per survey, or receivers that are
slow to answer — otherwise a burst of deliveries also delays pipeline and workflow jobs behind it. Each
concurrent slot needs a database connection, so raise the connection pool alongside it.
Retry behavior
BullMQ applies the following defaults to background jobs:
Individual workloads can add their own recovery behavior on top of these queue-level retries.
Webhook deliveries override the queue defaults with 5 attempts and an exponential backoff starting at 30
seconds (retries after roughly 30 seconds, 1, 2 and 4 minutes). A receiver that returns a 4xx status other
than 408 or 429, or a webhook URL that fails the SSRF policy, fails the job immediately without retrying;
network errors, timeouts, DNS failures, 408, 429 and 5xx statuses are retried. Deliveries to a webhook that was
deleted or unsubscribed from the event while the job was queued complete without a request.
Redirects depend on
DANGEROUSLY_ALLOW_WEBHOOK_INTERNAL_URLS. Left unset — the default, and the safe setting —
a 3xx response is refused rather than followed, and the attempt is retried in case the receiver’s configuration
is fixed. With the variable set, redirects are followed instead: note that following one turns the POST into a
GET, which many receivers answer with 405, and a 405 is then treated as a permanent rejection.
Workflows
Workflows use the shared Job Runner to execute runs and reconcile runs that lost their queue or worker handoff. No additional worker process is required.Configure action dependencies
Configure the services required by the workflow actions you use. For example, the Send email action uses your deployment’s SMTP transport and sender. ConfigureSMTP_HOST, SMTP_PORT, MAIL_FROM, MAIL_FROM_NAME,
and any authentication or TLS variables required by your provider.
See SMTP Configuration for the complete setup.
Retry and recovery behavior
A recurring reconciliation job runs every three minutes to recover workflow runs that lost their queue or worker handoff:- Queued runs older than two minutes are eligible for redispatch.
- Queued runs older than 24 hours are marked as failed instead of being redispatched indefinitely.
- Running runs with no executor activity for one hour are marked as failed and unfinished steps are skipped.
- Each reconciliation sweep processes at most 250 queued and 250 running candidates. Larger backlogs drain over subsequent sweeps.
Workflow run reconciliation acted with structured
counts for redispatched, aged-out, and recovered runs.
Troubleshoot workflow jobs
If workflow runs stay queued after the Job Runner is healthy:- Search for
BullMQ job failedentries wherejobNameisworkflow-run.processorworkflow-run.reconcile. - Search for
Workflow run reconciliation actedand inspect the queued and stuck-running counts. - Open the workflow’s Runs view to inspect the run status and step-level error.
Use a custom external worker
The default in-process worker is the supported setup. Formbricks does not ship a standalone worker command or container. The external-worker flags are only for custom deployments that provide their own consumer and register the complete Formbricks application job handlers. In such a custom deployment, configure the web/API processes to keep enqueueing without starting local workers:Troubleshoot the Job Runner
Formbricks writes structured logs to stdout and stderr. Search themsg field for these exact messages:
If background jobs stay queued:
- Confirm
REDIS_URLis present and identical on all web replicas. - Confirm at least one in-process worker is enabled, or that a compatible external worker is running.
- Look for
BullMQ runtime startedafter the latest deployment or restart. - Check Redis connection and
BullMQ job failedlogs. - Use the
jobNamefield to identify the affected workload and continue in its feature-specific logs or UI.
PROMETHEUS_ENABLED=1 or OTEL_EXPORTER_OTLP_ENDPOINT), webhook
deliveries also emit formbricks.webhook.delivery.total (a counter with outcome, event and status_class
attributes) and formbricks.webhook.delivery.duration (a histogram in milliseconds), so delivery failure rates
and receiver latency can be graphed and alerted on without reading logs. See
Monitoring for how to scrape them.
See Environment Variables for every Job Runner setting
and Monitoring for log collection guidance.