Workers

workers starts several processes that share the socket. It can't be combined with reload. Since uvicorn 0.51.0, sending SIGHUP to the main process restarts the workers one at a time, bringing each replacement up before retiring the old one, so a reload doesn't drop requests.

import uvicorn

if __name__ == "__main__":
    uvicorn.run(
        "app.main:app",
        host="0.0.0.0",
        port=8000,
        workers=4,  # defaults to $WEB_CONCURRENCY, or 1
        limit_max_requests=10000,  # recycle workers to contain memory leaks
        limit_max_requests_jitter=1000,  # so they don't all restart at once
        timeout_graceful_shutdown=30,
    )

Since uvicorn 0.50.0, any startup failure (app import error, port already in use, lifespan error) exits with code 3, and the supervisor stops instead of restarting a broken worker forever. Process managers can rely on that exit code.

Behind a Reverse Proxy

proxy_headers is on by default, but uvicorn only trusts X-Forwarded-For and X-Forwarded-Proto from the addresses in forwarded_allow_ips: 127.0.0.1 and ::1 unless you set it (or $FORWARDED_ALLOW_IPS). Set it to your proxy's IP or network, never * when clients can reach uvicorn directly. If the app is mounted under a path, set root_path.

A UNIX domain socket avoids TCP overhead between the proxy and uvicorn:

import uvicorn

if __name__ == "__main__":
    uvicorn.run(
        "app.main:app",
        uds="/tmp/uvicorn.sock",  # UNIX socket instead of TCP
        workers=4,
        log_level="warning",
        proxy_headers=True,
        forwarded_allow_ips="*"  # Trust all (configure nginx properly)
    )

Gunicorn

To run under Gunicorn, use the uvicorn-worker package (the uvicorn.workers module is deprecated). Gunicorn then manages the processes and uvicorn.run() isn't called. Some options, such as limit_concurrency, are not supported in this mode.

pip install uvicorn-worker
gunicorn app.main:app -w 4 -k uvicorn_worker.UvicornWorker

Graceful Shutdown

Uvicorn handles SIGINT and SIGTERM itself: it stops accepting connections, waits for in-flight requests up to timeout_graceful_shutdown, then runs the lifespan shutdown. To stop a server from your own code, set server.should_exit = True.

import asyncio

import uvicorn


async def main():
    config = uvicorn.Config("app.main:app", host="0.0.0.0", port=8000, timeout_graceful_shutdown=30)
    server = uvicorn.Server(config)
    serve_task = asyncio.create_task(server.serve())

    await asyncio.sleep(3600)  # or wait for any event: a queue message, a deploy hook...
    server.should_exit = True  # same graceful path as SIGTERM
    await serve_task


if __name__ == "__main__":
    asyncio.run(main())

Best Practices

Performance & Security

  • Install with pip install uvicorn[standard] for uvloop and httptools performance boost
  • Use multiple workers in production for CPU-bound tasks (or use Gunicorn as process manager)
  • Disable access logs in production for better performance (access_log=False)
  • Use environment variables for configuration via env_file parameter
  • Enable SSL/TLS for production deployments with proper certificates
  • Set appropriate log levels for different environments ("warning" for production)
  • Use reload only in development environments (never in production)
  • Configure forwarded_allow_ips carefully when behind a proxy - only trust known proxies
  • Set limit_max_requests to recycle workers and prevent memory leaks, and use limit_max_requests_jitter to stagger restarts
  • Configure timeout_graceful_shutdown for clean shutdown handling

Development Tips

  • Use reload_dirs to watch specific directories for changes
  • Set debug log level during development (log_level="debug")
  • Use different ports for different services
  • Keep development and production configs separate
  • Use reload_includes/reload_excludes for fine-grained reload control (requires watchfiles)
  • Use factory mode (factory=True) for apps that need fresh instances on reload

Deployment Options

  • Use uvicorn's built-in workers option for simple multi-process deployment, and send SIGHUP for a rolling restart
  • Startup failures (import error, port in use, lifespan error) exit with code 3, and the supervisor stops instead of restarting forever
  • For production, consider using Gunicorn with the uvicorn-worker package: gunicorn -k uvicorn_worker.UvicornWorker
  • Place behind a reverse proxy (nginx) for SSL termination, load balancing, and static files
  • Use UNIX domain sockets (uds) for reverse proxy communication to avoid TCP overhead