Deploy Langflow with multiple workers
By default, Langflow runs with a single worker process and stores build job state in memory.
A single-worker process is fine for development, but it doesn't scale when you run more than one worker. A flow build started on worker A cannot be polled or streamed from worker B because the in-memory job queue is per-process.
A multi-worker deployment runs more than one worker process on the same host. Concurrency can be increased by increasing the number of LANGFLOW_WORKERS, but each process keeps its own in-memory build queue unless you add a shared store.
A Redis-backed job queue stores build events in Redis Streams, so any worker can pick up and serve any job's events. To configure a multi-worker Langflow process, follow the steps to enable the Redis job queue.
After the Redis job queue is configured, you can optionally follow recommended Gunicorn settings to reduce memory use and keep workers healthy. This tuning applies to Linux production hosts only. On Windows and macOS, langflow run uses a single Uvicorn process.
Prerequisites
- Redis 6 or later, reachable from all Langflow worker processes.
- All workers configured with the same
LANGFLOW_JOB_QUEUE_TYPE. Mixed-mode deployments (some workers usingasyncio, others usingredis) are not supported. - A dedicated Redis database index for the job queue. The cache uses DB
0by default; the job queue defaults to DB1. Using the same index for both will cause key collisions.
Enable the Redis job queue
To enable the Redis job queue, set the following environment variables on all workers:
_10LANGFLOW_WORKERS=3 # any value > 1_10LANGFLOW_JOB_QUEUE_TYPE=redis_10LANGFLOW_REDIS_QUEUE_URL=redis://your-redis-host:6379/1
Redis authentication and TLS are only supported through LANGFLOW_REDIS_QUEUE_URL.
The individual host/port settings LANGFLOW_REDIS_QUEUE_HOST and LANGFLOW_REDIS_QUEUE_PORT create a plain, unauthenticated connection.
If you use a managed Redis service with auth or TLS, you must use LANGFLOW_REDIS_QUEUE_URL.
Example: multi-worker Docker Compose
This example runs three Langflow workers sharing a Redis job queue and a PostgreSQL database.
To run this example you need:
- Docker and Docker Compose (or Podman Desktop)
- At least 4 GB of memory and 2 CPUs available to the container runtime
If you are using Podman Desktop, the default machine might not have enough resources to run this stack. Before starting, increase the machine's CPU and memory allocation:
_10podman machine stop_10podman machine set --cpus 4 --memory 4096_10podman machine start
-
Paste the following example into a Docker Compose file named
docker-compose.yml:_44services:_44langflow:_44image: langflowai/langflow:1.10.0_44pull_policy: always_44ports:_44- "7860:7860"_44depends_on:_44redis:_44condition: service_healthy_44postgres:_44condition: service_started_44environment:_44- LANGFLOW_DATABASE_URL=postgresql://langflow:langflow@postgres:5432/langflow_44- LANGFLOW_CONFIG_DIR=/app/langflow_44- LANGFLOW_WORKERS=3 # any value > 1_44- LANGFLOW_GUNICORN_PRELOAD=true_44- LANGFLOW_JOB_QUEUE_TYPE=redis_44- LANGFLOW_REDIS_QUEUE_URL=redis://redis:6379/1_44- LANGFLOW_SUPERUSER=admin_44- LANGFLOW_SUPERUSER_PASSWORD=changeme_44- LANGFLOW_AUTO_LOGIN=False_44volumes:_44- langflow-data:/app/langflow_44_44redis:_44image: redis:7-alpine_44healthcheck:_44test: ["CMD", "redis-cli", "ping"]_44interval: 5s_44timeout: 3s_44retries: 5_44_44postgres:_44image: postgres:16-trixie_44environment:_44POSTGRES_USER: langflow_44POSTGRES_PASSWORD: langflow_44POSTGRES_DB: langflow_44volumes:_44- langflow-postgres:/var/lib/postgresql/data_44_44volumes:_44langflow-postgres:_44langflow-data: -
Start the services:
_10docker compose up -d -
Watch the logs until Langflow is ready:
_10docker compose logs -f langflowThese lines confirm a successful multi-worker boot:
_10[preload] initializing services in master_10[preload] master preload complete; workers will inherit shared state via COW_10✓ Launching Langflow... -
Create a superuser token using the
usernameandpasswordset in the Docker Compose file:_10TOKEN=$(curl -s -X POST http://localhost:7860/api/v1/login \_10-H "Content-Type: application/x-www-form-urlencoded" \_10-d "username=admin&password=changeme" \_10| python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])") -
In the same terminal session, verify the Redis queue is active:
_10curl -s -H "Authorization: Bearer $TOKEN" \_10http://localhost:7860/api/v1/monitor/job_queue | python3 -m json.toolA response looks like the following:
_21{_21"backend": "redis",_21"active_jobs": 0,_21"bridge_count": 0,_21"consumer_wrapper_count": 0,_21"background_task_count": 0,_21"cancel_dispatcher_running": true,_21"cancel_stats": {_21"published": 0,_21"marker_hit": 0,_21"dispatched_owned": 0,_21"dispatched_foreign": 0,_21"publish_errors": 0,_21"dispatcher_reconnects": 0,_21"dispatcher_internal_errors": 0,_21"polling_watchdog_kills": 0,_21"activity_touch_errors": 0,_21"activity_get_errors": 0,_21"activity_parse_errors": 0_21}_21}backend: redisconfirms the queue is using Redis, andcancel_dispatcher_running: trueconfirms the cross-worker cancel channel is active. -
In the same terminal session, poll the
monitor/job_queueendpoint:_10while true; do_10clear_10curl -s -H "Authorization: Bearer $TOKEN" \_10http://localhost:7860/api/v1/monitor/job_queue | python3 -m json.tool_10sleep 1_10done -
Open the Langflow UI and build a flow by sending a message to the flow in the Playground. The number of
active_jobsreported by themonitor/job_queueendpoint increases, confirming your Redis queue is working.
Verify cross-worker cancellation
To verify that cancellation works across workers, trigger a build with the API to capture the job_id and then cancel it.
-
Use the API to build a flow by its
flow_id:_10curl -s -X POST "http://localhost:7860/api/v1/build/af7dc029-279e-4742-8419-1ac23898afdd/flow" \_10-H "Authorization: Bearer $TOKEN" \_10-H "Content-Type: application/json" \_10-d '{"inputs": {"input_value": "hello"}, "stream": false}' | python3 -m json.toolResponse:
_10{_10"job_id": "1af960e9-12d5-48ec-9860-8a90a16f0b55"_10}The returned
job_idcan be used to cancel build jobs in the queue. -
To cancel a build job, send a request with the
job_id:_10curl -s -X POST http://localhost:7860/api/v1/build/1af960e9-12d5-48ec-9860-8a90a16f0b55/cancel \_10-H "Authorization: Bearer $TOKEN" | python3 -m json.toolResponse:
_10{_10"success": true,_10"message": "Flow build cancelled successfully"_10} -
Confirm the
monitor/job_queueendpoint reports the cancellation:_21{_21"backend": "redis",_21"active_jobs": 0,_21"bridge_count": 0,_21"consumer_wrapper_count": 0,_21"background_task_count": 0,_21"cancel_dispatcher_running": true,_21"cancel_stats": {_21"published": 1,_21"marker_hit": 0,_21"dispatched_owned": 0,_21"dispatched_foreign": 2,_21"publish_errors": 0,_21"dispatcher_reconnects": 0,_21"dispatcher_internal_errors": 0,_21"polling_watchdog_kills": 0,_21"activity_touch_errors": 0,_21"activity_get_errors": 0,_21"activity_parse_errors": 0_21}_21}dispatched_foreignincrements when the signal is dispatched to a job owned by a different worker, which confirms the cross-worker cancel path is working.For more information, see Monitor the job queue.
Troubleshoot
See Troubleshoot multi-worker deployments.
Configuration reference
| Variable | Default | Description |
|---|---|---|
LANGFLOW_JOB_QUEUE_TYPE | asyncio | Job queue backend. Set to redis to enable the cross-worker queue. |
LANGFLOW_REDIS_QUEUE_URL | Not set | Full Redis connection URL. Takes priority over HOST/PORT/DB when set. Use this for any Redis instance that requires authentication or TLS. |
LANGFLOW_REDIS_QUEUE_HOST | LANGFLOW_REDIS_HOST | Redis host for the job queue. Falls back to the general Redis host setting. Does not support auth or TLS (use LANGFLOW_REDIS_QUEUE_URL instead for secured instances). |
LANGFLOW_REDIS_QUEUE_PORT | LANGFLOW_REDIS_PORT | Redis port for the job queue. Falls back to the general Redis port setting. |
LANGFLOW_REDIS_QUEUE_DB | 1 | Redis database index for the job queue. Must differ from the cache database index (default 0) to avoid key collisions. |
LANGFLOW_REDIS_QUEUE_TTL | 3600 | TTL in seconds for job stream and ownership keys in Redis. |
LANGFLOW_REDIS_QUEUE_STARTUP_GRACE_S | 30.0 | Seconds a consumer waits for the producer's first write before treating a missing stream as end-of-stream. Increase this if your workers have slow cold-starts. Setting to 0 removes the grace period so a not-yet-created stream is treated as EOF immediately. |
LANGFLOW_REDIS_QUEUE_CANCEL_CHANNEL_ENABLED | True | When true, each worker runs a Redis pub/sub dispatcher so that POST /build/{id}/cancel cancels a build on any worker, not just the one that received the request. Closing a browser tab also signals cancel cross-worker. |
LANGFLOW_REDIS_QUEUE_CANCEL_MARKER_TTL | 60 | TTL in seconds for the cancel-marker key. The marker closes a race where a cancel signal is published before the target worker's dispatcher has subscribed. A non-positive value is rejected at startup. |
LANGFLOW_REDIS_QUEUE_POLLING_STALE_THRESHOLD_S | 90.0 | Seconds without client activity before the watchdog cancels an abandoned polling build. Set to 0 to disable the watchdog entirely. |
LANGFLOW_REDIS_QUEUE_POLLING_WATCHDOG_INTERVAL_S | 15.0 | How often in seconds the watchdog scans for stale jobs. Lower values reclaim resources faster at the cost of more Redis reads. |
LANGFLOW_GUNICORN_PRELOAD | False | Experimental. Loads the app in the Gunicorn master process before workers fork, reducing per-worker startup overhead. Pairs well with LANGFLOW_WORKERS. Non-Windows only. |
Monitor the job queue
The GET /monitor/job_queue endpoint returns a metrics snapshot for the running worker. It requires superuser authentication and returns HTTP 403 otherwise.
_10curl -H "Authorization: Bearer $LANGFLOW_SUPERUSER_TOKEN" \_10 http://localhost:7860/api/v1/monitor/job_queue
Example response for the Redis backend:
_21{_21 "backend": "redis",_21 "active_jobs": 2,_21 "bridge_count": 1,_21 "consumer_wrapper_count": 1,_21 "background_task_count": 0,_21 "cancel_dispatcher_running": true,_21 "cancel_stats": {_21 "published": 5,_21 "marker_hit": 1,_21 "dispatched_owned": 3,_21 "dispatched_foreign": 2,_21 "publish_errors": 0,_21 "dispatcher_reconnects": 0,_21 "dispatcher_internal_errors": 0,_21 "polling_watchdog_kills": 0,_21 "activity_touch_errors": 0,_21 "activity_get_errors": 0,_21 "activity_parse_errors": 0_21 }_21}
For the in-memory (asyncio) backend, only backend and active_jobs are returned.
Response body
The Redis backend response includes the following fields:
| Field | Description |
|---|---|
backend | The active job queue backend: redis or asyncio. |
active_jobs | Number of jobs currently owned by this worker. |
bridge_count | Number of active Redis stream bridge tasks on this worker. A bridge reads events from the local asyncio.Queue and writes them to Redis Streams so any worker can consume them. |
consumer_wrapper_count | Number of active Redis stream consumer wrappers on this worker. A consumer wrapper reads events from a Redis Stream for cross-worker polling or streaming requests. |
background_task_count | Number of fire-and-forget background tasks currently running (cancel cleanup, marker checks). |
cancel_dispatcher_running | Whether the per-worker Redis pub/sub dispatcher is active. If false, this worker cannot receive cross-worker cancel signals. The dispatcher reconnects automatically with exponential backoff (capped at 30s), so a brief false during a Redis restart is expected. |
cancel_stats.published | Number of cancel signals published to the Redis pub/sub channel by this worker. |
cancel_stats.marker_hit | Number of times a cancel marker key was found, catching cancels that raced the dispatcher. |
cancel_stats.dispatched_owned | Number of cancel signals dispatched to jobs owned by this worker. |
cancel_stats.dispatched_foreign | Number of cancel signals dispatched to jobs owned by a different worker. A non-zero value confirms cross-worker cancellation is working. |
cancel_stats.publish_errors | Number of Redis errors on the cancel publish path. Persistent non-zero values indicate a Redis connectivity problem. |
cancel_stats.dispatcher_reconnects | Number of times the cancel dispatcher has reconnected after a Redis pub/sub error. |
cancel_stats.dispatcher_internal_errors | Number of unexpected errors inside the cancel dispatcher (not Redis disconnects). Non-zero values indicate a bug; check logs for details. |
cancel_stats.polling_watchdog_kills | Number of abandoned polling builds reclaimed by the watchdog. Non-zero is normal under load. A very high count may indicate frequent client disconnects (consider increasing LANGFLOW_REDIS_QUEUE_POLLING_STALE_THRESHOLD_S). |
cancel_stats.activity_touch_errors | Number of errors writing the client heartbeat key to Redis. |
cancel_stats.activity_get_errors | Number of errors reading the client heartbeat key from Redis. |
cancel_stats.activity_parse_errors | Number of malformed heartbeat values encountered by the watchdog. |
Recommended Gunicorn settings
This tuning is optional, and it applies to Linux only. It does not replace the Redis job queue.
On Windows and macOS, langflow run uses one Uvicorn process, so LANGFLOW_GUNICORN_PRELOAD and GUNICORN_CMD_ARGS have no effect.
These recommendations are starting points from Langflow engineering benchmarks on Linux multi-worker deployments.
After you apply these values, monitor performance, and then make adjustments using htop or btop while you run flows.
Add the starter values to your server's .env file after the Redis job queue settings, then restart Langflow. Copy the block that best matches your server:
- Small / dev (4–8 GB RAM)
- Standard API (12 GB RAM)
- Heavy multi-agent (24 GB+ RAM)
Start with fewer workers so Langflow and local databases do not hit OOM.
_10LANGFLOW_WORKERS=5_10LANGFLOW_WORKER_TIMEOUT=120_10LANGFLOW_GUNICORN_PRELOAD=true_10GUNICORN_CMD_ARGS="--max-requests 100 --max-requests-jitter 20"
A balanced default for a dedicated Langflow host.
_10LANGFLOW_WORKERS=15_10LANGFLOW_WORKER_TIMEOUT=300_10LANGFLOW_GUNICORN_PRELOAD=true_10GUNICORN_CMD_ARGS="--max-requests 250 --max-requests-jitter 50"
Agent loops use more RAM per request, so a lower --max-requests value restarts workers more often to keep long-term usage consistent.
_10LANGFLOW_WORKERS=30_10LANGFLOW_WORKER_TIMEOUT=600_10LANGFLOW_GUNICORN_PRELOAD=true_10GUNICORN_CMD_ARGS="--max-requests 150 --max-requests-jitter 30"
LANGFLOW_WORKERS— How many Gunicorn worker processes run for concurrency.LANGFLOW_WORKER_TIMEOUT— How long a worker may handle a single request before Gunicorn kills it. Raise it if you expect long agent runs.LANGFLOW_GUNICORN_PRELOAD— Loads the app once in the Gunicorn master before workers fork so Linux can share memory across workers through Copy-on-Write. Recommended to leave enabled in multi-worker deployments for memory savings. Safe to leave off; behavior matches older releases whenfalse.GUNICORN_CMD_ARGS— Recycles workers after a set number of requests so memory usage growth does not continuously accumulate.--max-requestsrestarts a worker after it processes that many requests;--max-requests-jitteradds a random extra 0–N requests on top of that limit for each worker. Spreading restarts over time avoids every worker reloading at once. If RAM usage increases over time, lower--max-requestsbefore you lower worker count.
For more information, see the Scaling Langflow blog post.