nginx + Uvicorn + FastAPI + systemd

October 21st, 2023

A cheat-sheet on how to run a FastAPI application within Uvicorn as a asgi-server under the systemd control and integrate it with nginx. Some more comments below (privacy, performance).

Create a virtual environment. Prerequisite (ubuntu):

apt install python3-venv

Installation of Uvicorn and FastAPI:

mkdir <application_dir>
cd <application_dir>/
python3 -m venv .
. ./bin/activate
pip install uvicorn
pip install fastapi

Create an application, e.g.

from fastapi import FastAPI
app = FastAPI()

async def hello():
    return {"message": "hello"}

Monitoring via systemd:


Description=uvicorn daemon

#ExecStart=<application_dir>/bin/uvicorn --uds <unix_socket_path> --reload --root-path <mount_path> hello:app
ExecStart=<application_dir>/bin/uvicorn --uds <unix_socket_path> --workers <number_of_workers> --root-path <mount_path> hello:app



Note: (TODO) access and error logs need to be configured here as well.

Activate and start the service:

systemctl enable --now uvicorn.service

Test the application (unix socket - Uvicorn - FastAPI):

curl -X GET --unix-socket <unix_socket_path> http://does-not-matter/

Integrate it with nginx:

http {

    upstream fastapi {
        server unix:<unix_socket_path>;


server {

    location /... {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_pass http://fastapi/;



Details on the proxy_pass:


Consider blocking an automatically generated documentation of your application, e.g. by setting the three endpoints to None when instantiating a FastAPI application app = FastAPI(openapi_url=None, docs_url=None, redoc_url=None). Another approach would be to restrict the access to these resources, like the one discussed on GitHub.


Given the application:

from fastapi import FastAPI
from time import sleep

app = FastAPI()

async def root():
    return {"message": "hello"}

And the time measuring command:

$ time -p { seq 1 200 | \
  xargs -I % -n1 -P10 \
  sh -c 'curl -s -o /dev/null -X GET --unix-socket <unix_socket_path> http://does-not-matter/; echo -n %,;'; echo done; }


Note: don't forget the semicolons. They are really required after both: the last command of the sh -c block and the last command of the time -p {} argument.


Comparison to flask

Just after a few tests: I don't see huge differences. Actually, flask did provide a solution for injecting environment-specific data (like database credentials). It seems there is no solution like this in FastAPI:


Instead, you may read such variables out of a file using additional code or libraries like pydantic as described in the offical docs.

Next: OpenVPN with global IPv6 addressing

Previous: Upload to S3 bucket from bash

Main Menu