Uvicorn with FastAPI: Host, Port, Reload, and Workers
How to use Uvicorn's host, port, reload, and workers options with FastAPI, and why reload and multiple worker processes cannot be combined.
Uvicorn is a common ASGI server for FastAPI applications. The command-line options --host, --port, --reload, and --workers control where the server listens, whether it restarts after code changes, and how many worker processes handle requests. Getting these right matters because the command that works on a laptop can be wrong for production if it binds to the wrong interface or enables reload alongside multiple workers.
Running Uvicorn from the Command Line
The most direct way to start a FastAPI app with Uvicorn is the uvicorn command. For a module main.py containing an app instance named app, the default command is:
uvicorn main:app
This binds to 127.0.0.1 on port 8000, uses one worker process, and does not reload source files. To change the host and port, pass explicit options:
uvicorn main:app --host 0.0.0.0 --port 8080
To restart automatically when source files change, add --reload:
uvicorn main:app --reload
To run multiple worker processes, use --workers:
uvicorn main:app --workers 4
These options can be combined, with one exception: reload mode is not supported when --workers is greater than 1. The reloader uses a separate process that watches files and restarts the server; it does not manage a pool of workers. If you try to run reload with multiple workers, Uvicorn rejects that combination.
Setting Host and Port
The host determines which network interface Uvicorn listens on. The default 127.0.0.1 only accepts connections from the same machine. That is fine for local development, but if you run the server in a container or on a remote server, you usually need 0.0.0.0 so the server accepts connections from outside:
uvicorn main:app --host 0.0.0.0
Binding to 0.0.0.0 means the server listens on all available network interfaces; it does not guarantee the server is publicly exposed. The actual exposure also depends on your firewall and network configuration. For local testing, 127.0.0.1 is usually enough. For a development container that needs to be reachable from the host machine, use 0.0.0.0.
The port can be any valid TCP port. Port 8000 is the default; choose another if that port is already in use. If you run multiple Uvicorn instances on the same machine, each must use a different port.
Using Reload for Development
--reload makes Uvicorn watch your Python files and restart the server whenever a file changes. This is a development convenience so you do not have to restart the process manually after every edit.
uvicorn main:app --reload
Uvicorn implements reload by running a separate watch process that monitors the file system. When a change is detected, it stops the server process and starts a new one. This means application state is lost on every reload, which is expected during development.
Reload works best while iterating on code locally. It is not intended for production because:
- It adds file-watching overhead.
- It can restart the server unexpectedly if a file is touched.
- It does not run multiple worker processes.
The reloader watches the directory where the application is imported from. If you have large directories or generated files, use --reload-dir to restrict the watch scope.
Using Workers for Production
--workers tells Uvicorn to start multiple worker processes, each running a separate copy of your application. With multiple workers listening on the same socket, the operating system decides which worker accepts an incoming connection, and each worker has its own event loop.
uvicorn main:app --workers 4
Multiple worker processes are useful when your application has CPU-bound work or when you want to take advantage of multiple CPU cores. FastAPI endpoints are often asynchronous, so one worker can handle many concurrent I/O-bound requests, but CPU-bound work is limited by a single Python process.
The number of workers depends on your workload and available resources. A common starting point is one worker per CPU core, but you should measure your application under load. Each worker loads the application and its dependencies into memory, so too many workers can exhaust memory or cause excessive context switching.
If your application uses in-memory state or creates a database connection pool, each worker has its own copy. Do not assume state is shared across workers; store shared data in an external service such as Redis or a database.
Configuring Uvicorn Programmatically
Instead of using the command line, you can start Uvicorn from Python with uvicorn.run(). This is useful when you need to set configuration dynamically or embed server startup in a script.
import uvicorn if __name__ == "__main__": uvicorn.run( "main:app", host="0.0.0.0", port=8080, reload=True )
The reload and workers parameters follow the same rule as the command line: when reload is enabled, keep worker count at one. For multiple workers, set workers=N and reload=False.
When you pass a string like "main:app", Uvicorn imports the module and locates the app. You can also pass the app object directly:
import uvicorn from main import app if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8080)
Passing the app object directly is convenient in a single-file script, but it can cause problems with reload because the reloader needs to re-import the module. Use the string form when reload=True.
Why Reload and Multiple Workers Do Not Mix
The conflict between reload and multiple worker processes is not arbitrary. Reload relies on a watch process that restarts the server when files change. In production, you normally want multiple workers to increase capacity, and you do not want code changes to restart those workers. In development, you want reload with one worker so edited code is picked up quickly.
If you need to test multiple workers locally, run without reload and restart manually after changes. For production deployments, use multiple workers or a process manager such as Gunicorn.
Common Pitfalls and Runtime Behavior
One frequent mistake is trying to use --reload with --workers 4. Uvicorn rejects this combination; run without reload when testing multiple workers.
Another pitfall is binding to 127.0.0.1 when the server needs to be reachable from another machine. Inside a Docker container, the loopback interface is separate from the host, so --host 127.0.0.1 will not accept connections from the host. Use --host 0.0.0.0 in that case.
Remember that worker processes do not share memory. Any in-memory cache or local counter will be per worker, not global.
Production Considerations
In production, Uvicorn is often placed behind a reverse proxy such as Nginx or a load balancer. The proxy handles TLS termination, request routing, and sometimes static files, while Uvicorn serves the FastAPI application.
When multiple Uvicorn workers are listening, the operating system distributes incoming connections among them. Keep-alive connections may continue to use the same worker for subsequent requests on that connection, which is normal. If you need more control over worker lifecycle, graceful shutdown, or process supervision, use Gunicorn with Uvicorn workers:
gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app
This combines Gunicorn's process management with Uvicorn's ASGI support and is a common production setup for FastAPI applications.