Skip to main content
Version: 1.10.x

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 using asyncio, others using redis) are not supported.
  • A dedicated Redis database index for the job queue. The cache uses DB 0 by default; the job queue defaults to DB 1. 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:


_10
LANGFLOW_WORKERS=3 # any value > 1
_10
LANGFLOW_JOB_QUEUE_TYPE=redis
_10
LANGFLOW_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:

tip

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:


_10
podman machine stop
_10
podman machine set --cpus 4 --memory 4096
_10
podman machine start

  1. Paste the following example into a Docker Compose file named docker-compose.yml:


    _44
    services:
    _44
    langflow:
    _44
    image: langflowai/langflow:1.10.0
    _44
    pull_policy: always
    _44
    ports:
    _44
    - "7860:7860"
    _44
    depends_on:
    _44
    redis:
    _44
    condition: service_healthy
    _44
    postgres:
    _44
    condition: service_started
    _44
    environment:
    _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
    _44
    volumes:
    _44
    - langflow-data:/app/langflow
    _44
    _44
    redis:
    _44
    image: redis:7-alpine
    _44
    healthcheck:
    _44
    test: ["CMD", "redis-cli", "ping"]
    _44
    interval: 5s
    _44
    timeout: 3s
    _44
    retries: 5
    _44
    _44
    postgres:
    _44
    image: postgres:16-trixie
    _44
    environment:
    _44
    POSTGRES_USER: langflow
    _44
    POSTGRES_PASSWORD: langflow
    _44
    POSTGRES_DB: langflow
    _44
    volumes:
    _44
    - langflow-postgres:/var/lib/postgresql/data
    _44
    _44
    volumes:
    _44
    langflow-postgres:
    _44
    langflow-data:

  2. Start the services:


    _10
    docker compose up -d

  3. Watch the logs until Langflow is ready:


    _10
    docker compose logs -f langflow

    These 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...

  4. Create a superuser token using the username and password set in the Docker Compose file:


    _10
    TOKEN=$(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'])")

  5. In the same terminal session, verify the Redis queue is active:


    _10
    curl -s -H "Authorization: Bearer $TOKEN" \
    _10
    http://localhost:7860/api/v1/monitor/job_queue | python3 -m json.tool

    A 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: redis confirms the queue is using Redis, and cancel_dispatcher_running: true confirms the cross-worker cancel channel is active.

  6. In the same terminal session, poll the monitor/job_queue endpoint:


    _10
    while true; do
    _10
    clear
    _10
    curl -s -H "Authorization: Bearer $TOKEN" \
    _10
    http://localhost:7860/api/v1/monitor/job_queue | python3 -m json.tool
    _10
    sleep 1
    _10
    done

  7. Open the Langflow UI and build a flow by sending a message to the flow in the Playground. The number of active_jobs reported by the monitor/job_queue endpoint 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.

  1. Use the API to build a flow by its flow_id:


    _10
    curl -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.tool

    Response:


    _10
    {
    _10
    "job_id": "1af960e9-12d5-48ec-9860-8a90a16f0b55"
    _10
    }

    The returned job_id can be used to cancel build jobs in the queue.

  2. To cancel a build job, send a request with the job_id:


    _10
    curl -s -X POST http://localhost:7860/api/v1/build/1af960e9-12d5-48ec-9860-8a90a16f0b55/cancel \
    _10
    -H "Authorization: Bearer $TOKEN" | python3 -m json.tool

    Response:


    _10
    {
    _10
    "success": true,
    _10
    "message": "Flow build cancelled successfully"
    _10
    }

  3. Confirm the monitor/job_queue endpoint 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_foreign increments 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

VariableDefaultDescription
LANGFLOW_​JOB_​QUEUE_​TYPEasyncioJob queue backend. Set to redis to enable the cross-worker queue.
LANGFLOW_​REDIS_​QUEUE_​URLNot setFull 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_​HOSTLANGFLOW_​REDIS_​HOSTRedis 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_​PORTLANGFLOW_​REDIS_​PORTRedis port for the job queue. Falls back to the general Redis port setting.
LANGFLOW_​REDIS_​QUEUE_​DB1Redis database index for the job queue. Must differ from the cache database index (default 0) to avoid key collisions.
LANGFLOW_​REDIS_​QUEUE_​TTL3600TTL in seconds for job stream and ownership keys in Redis.
LANGFLOW_​REDIS_​QUEUE_​STARTUP_​GRACE_​S30.0Seconds 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_​ENABLEDTrueWhen 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_​TTL60TTL 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_​S90.0Seconds 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_​S15.0How often in seconds the watchdog scans for stale jobs. Lower values reclaim resources faster at the cost of more Redis reads.
LANGFLOW_​GUNICORN_​PRELOADFalseExperimental. 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.


_10
curl -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:

FieldDescription
backendThe active job queue backend: redis or asyncio.
active_​jobsNumber of jobs currently owned by this worker.
bridge_​countNumber 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_​countNumber 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_​countNumber of fire-and-forget background tasks currently running (cancel cleanup, marker checks).
cancel_​dispatcher_​runningWhether 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.publishedNumber of cancel signals published to the Redis pub/sub channel by this worker.
cancel_​stats.marker_​hitNumber of times a cancel marker key was found, catching cancels that raced the dispatcher.
cancel_​stats.dispatched_​ownedNumber of cancel signals dispatched to jobs owned by this worker.
cancel_​stats.dispatched_​foreignNumber of cancel signals dispatched to jobs owned by a different worker. A non-zero value confirms cross-worker cancellation is working.
cancel_​stats.publish_​errorsNumber of Redis errors on the cancel publish path. Persistent non-zero values indicate a Redis connectivity problem.
cancel_​stats.dispatcher_​reconnectsNumber of times the cancel dispatcher has reconnected after a Redis pub/sub error.
cancel_​stats.dispatcher_​internal_​errorsNumber of unexpected errors inside the cancel dispatcher (not Redis disconnects). Non-zero values indicate a bug; check logs for details.
cancel_​stats.polling_​watchdog_​killsNumber 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_​errorsNumber of errors writing the client heartbeat key to Redis.
cancel_​stats.activity_​get_​errorsNumber of errors reading the client heartbeat key from Redis.
cancel_​stats.activity_​parse_​errorsNumber of malformed heartbeat values encountered by the watchdog.
tip

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:

Start with fewer workers so Langflow and local databases do not hit OOM.


_10
LANGFLOW_WORKERS=5
_10
LANGFLOW_WORKER_TIMEOUT=120
_10
LANGFLOW_GUNICORN_PRELOAD=true
_10
GUNICORN_CMD_ARGS="--max-requests 100 --max-requests-jitter 20"

  • 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 when false.
  • GUNICORN_CMD_ARGS — Recycles workers after a set number of requests so memory usage growth does not continuously accumulate. --max-requests restarts a worker after it processes that many requests; --max-requests-jitter adds 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-requests before you lower worker count.

For more information, see the Scaling Langflow blog post.

Search