Running FastAPI Applications
FastAPI applications are typically run using an ASGI server which is one of the most common servers for running FastAPI is uvicorn.
It’s lightweight, fast, and specifically designed for ASGI frameworks like FastAPI.
What is Uvicorn?
uvicorn is an ASGI (Asynchronous Server Gateway Interface) server implementation that serves FastAPI applications by handling incoming HTTP connections, managing concurrency, and forwarding requests to FastAPI’s internal handlers.
You can install uvicorn directly from pip using the following command:
pip install uvicorn
Once installed, you can start your FastAPI app by pointing uvicorn to the module and the app object as follows:
uvicorn main:app
In this example, main is the main Python file (without .py) and app is the FastAPI instance inside that file.
You may see some of
PyFuexamples runningapp:app, this is totally normal.
You also can add options to control how the server runs:
uvicorn main:app --host 0.0.0.0 --port 8080 --reload
--hostoption defines the interface to bind (0.0.0.0 exposes it publicly).--portoption sets the port.--reloadenables auto-reloading for development.
You can also run FastAPI apps directly if you place the server run logic inside your main.py or app.py files, for example:
# main.py
import uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=8080, reload=True)
Now you can simply start the application using:
python main.py
In production environments, FastAPI applications are typically deployed behind a reverse proxy or a load balancer.
The reverse proxy (such as Nginx or Traefik) handles incoming client requests, manages SSL termination, provides routing, and forwards traffic to the FastAPI server running under an ASGI server like uvicorn or gunicorn with ASGI workers.
Why how you run it matters from an offensive security perspective
The flags on this page decide how exposed the app is, and the convenient defaults are the insecure ones.
--host 0.0.0.0binds every interface. It is fine on a laptop and dangerous on a multi-homed host or a container with a routable address, because the app you thought was internal is now answering the public network directly, in front of any reverse proxy and any auth the proxy was supposed to add. During recon, an app reachable on0.0.0.0that should have been behind the proxy is a frequent finding. Bind to127.0.0.1and let the proxy be the only public listener.--reloadis a development feature that must never reach production. It watches the filesystem and re-executes code on change, so anywhere an attacker can write a.pyfile in the project tree (an upload directory, a writable mount, a path-traversal write) the next reload runs it. It also signals “this is a dev deployment,” which usually means more debugging surface nearby.- The reverse proxy makes request headers untrusted in a new way. Once traffic arrives through Nginx or Traefik, the app sees proxy-set headers like
X-Forwarded-ForandX-Forwarded-Host. If the application trusts those for access decisions or logging, a client that can reach the app directly (see the0.0.0.0point) can forge them. Header-trust bypasses are their own class, see Authentication Bypass via Development Environment Headers Abuse.
The short version: the way an app is launched is part of its attack surface. A uvicorn main:app --host 0.0.0.0 --reload left running in production is an exposure finding before you have looked at a single route.
Mitigation
Run the app bound to loopback and let a single reverse proxy be the only public listener, never enable --reload outside development, and treat proxy-set headers as untrusted unless a proxy you control strips and rewrites them. Disable interactive docs in production if they are not needed (see Exposed FastAPI Documentation Page), and keep the production launch command free of development conveniences.
import uvicorn
if __name__ == "__main__":
# loopback only: the reverse proxy terminates TLS and adds auth in front
uvicorn.run("main:app", host="127.0.0.1", port=8080, reload=False)