Use this file to discover all available pages before exploring further.
Running multiple Prefect server instances enables high availability and distributes load across your infrastructure. This guide covers configuration and deployment patterns for scaling self-hosted Prefect.
Multiple API server instances - Handle UI and API requests
Background services - Runs the scheduler, automation triggers, and other loop services. Can be scaled horizontally by running multiple instances coordinated through Redis
PostgreSQL database - Stores all persistent data and synchronizes state across servers
Redis - Distributes events between services and coordinates background service work
Load balancer - Routes traffic to healthy API instances (e.g. NGINX or Traefik)
PostgreSQL version 14.9 or higher is required for multi-server deployments. SQLite does not support the features needed for state synchronization across multiple servers.
For Redis instances that require an encrypted connection, you can enable SSL/TLS:
export PREFECT_REDIS_MESSAGING_SSL="true"
Alternatively, configure the Redis connection with a single URL instead of individual fields. When PREFECT_REDIS_MESSAGING_URL is set, it takes precedence and the individual host, port, db, username, password, and SSL fields are ignored:
Prefect uses Docket to coordinate background services like the scheduler, late run detection, and automation triggers. By default, Docket uses in-memory storage (memory://), which only works for single-server deployments.For high-availability deployments, configure Docket to use Redis:
The Docket URL can use the same Redis instance as the messaging configuration above, but you may use a different database number (e.g., /1 instead of /0) to keep the data separate.
For optimal performance, run API servers and background services separately:API servers (multiple instances):
prefect server start --host 0.0.0.0 --port 4200 --no-services
Background services:
prefect server services start
For high-volume deployments, consider reducing the event retention period from the default 7 days to prevent rapid database growth. See database maintenance for configuration details.
For high availability and throughput, you can run multiple prefect server services start processes in parallel. Prefect uses Docket (backed by Redis) to coordinate work across background service processes so that periodic work (like scheduling, late run detection, and automation trigger evaluation) runs exactly once per interval even when multiple processes are running.To run multiple background service processes:
Configure PREFECT_SERVER_DOCKET_URL to point at a shared Redis instance (see Docket URL for background services). The default in-memory backend (memory://) is only safe for a single process.
Ensure every background service process connects to the same PostgreSQL database, Redis messaging instance, and Docket URL as the API servers.
Start one prefect server services start process per replica. Each replica runs the same set of enabled services; Docket ensures only one replica picks up each scheduled run.
Do not run multiple background service processes without configuring PREFECT_SERVER_DOCKET_URL to use Redis. With the default memory:// backend each process schedules its own work independently, which causes duplicate scheduled runs, duplicate automation actions, and other correctness problems.
All services run together in a single prefect server services start process by default. To dedicate a process to a specific subset of services (for example, to scale a noisy neighbor independently), disable the services you don’t want to run on that process with their *_ENABLED environment variable.List all services and their enable/disable environment variable:
prefect server services ls
For example, to run a process that only handles the scheduler and late run detection:
export PREFECT_SERVER_SERVICES_CANCELLATION_CLEANUP_ENABLED=falseexport PREFECT_SERVER_SERVICES_PAUSE_EXPIRATIONS_ENABLED=falseexport PREFECT_SERVER_SERVICES_FOREMAN_ENABLED=falseexport PREFECT_SERVER_SERVICES_TRIGGERS_ENABLED=falseexport PREFECT_SERVER_SERVICES_EVENT_PERSISTER_ENABLED=falseexport PREFECT_SERVER_SERVICES_TASK_RUN_RECORDER_ENABLED=false# ...disable any other services you don't want on this processprefect server services start
Then run another process with the complementary set of services enabled. As long as every enabled service is running on at least one process (and all processes share the same Docket Redis), every enabled service continues to operate.
Some services maintain their own at-least-once semantics at the database or Redis level rather than relying on Docket’s run-once guarantee. Running multiple processes with the same service enabled is supported, but starting with one process per service (and scaling up only when you observe a specific bottleneck) keeps operations simple.
When running migrations on large database instances (especially where tables like events, flow_runs, or task_runs can reach millions of rows), the default database timeout of 10 seconds may not be sufficient for creating indexes.If you encounter a TimeoutError during migrations, increase the database timeout:
# Set timeout to 10 minutes (adjust based on your database size)export PREFECT_API_DATABASE_TIMEOUT=600# Then run the migrationprefect server database upgrade -y
For Docker deployments:
docker run -e PREFECT_API_DATABASE_TIMEOUT=600 prefecthq/prefect:latest prefect server database upgrade -y
Index creation time scales with table size. A database with millions of events may require 30+ minutes for some migrations. If a migration fails due to timeout, you may need to manually clean up any partially created indexes before retrying.
If a migration times out while creating indexes, you may need to manually complete it. For example, if migration 7a73514ca2d6 fails:
First, check which indexes were partially created:
SELECT indexname FROM pg_indexes WHERE tablename = 'events' AND indexname LIKE 'ix_events%';
Manually create the missing indexes using CONCURRENTLY to avoid blocking:
-- Drop any partial indexes from the failed migrationDROP INDEX IF EXISTS ix_events__event_related_occurred;DROP INDEX IF EXISTS ix_events__related_resource_ids;-- Create the new indexesCREATE INDEX CONCURRENTLY ix_events__related_gin ON events USING gin(related);CREATE INDEX CONCURRENTLY ix_events__event_occurred ON events (event, occurred);CREATE INDEX CONCURRENTLY ix_events__related_resource_ids_gin ON events USING gin(related_resource_ids);
Mark the migration as complete:
UPDATE alembic_version SET version_num = '7a73514ca2d6';
Only use manual recovery if increasing the timeout and retrying the migration doesn’t work. Always verify the correct migration version and index definitions from the migration files.