How to host a FastAPI app on your own domain

uvicorn --reload is for your laptop. On a server you want uvicorn with a couple of workers, the port the host gives you, the proxy's headers understood, and HTTPS in front. Here is each part, with the code.

6 min readIntermediateUpdated Examples tested with FastAPI 0.161 and uvicorn 0.54

1. The app, and what to install

Keep the app in app.py as usual:

Python
# app.py
import os

from dotenv import load_dotenv
from fastapi import FastAPI

load_dotenv()
app = FastAPI()

@app.get("/")
def home():
    return {"ok": True}
Text
# requirements.txt
fastapi
uvicorn[standard]
python-dotenv

uvicorn[standard] adds a faster event loop and HTTP parser, and WebSocket support. Plain uvicorn also works, more slowly and without WebSockets.

2. Start it the way a server should

--reload watches your files and restarts on every change. On a server it costs memory, and it runs a single process. Start uvicorn from a small main.py instead, which is the file that gets run:

Python
# main.py: starts app.py under uvicorn
import os

import uvicorn

if __name__ == "__main__":
    uvicorn.run(
        "app:app",
        host="0.0.0.0",
        port=int(os.environ.get("SERVER_PORT") or os.environ.get("PORT") or 8000),
        workers=2,
        proxy_headers=True,
        forwarded_allow_ips="*",
    )
  • host="0.0.0.0": listen on every interface. An app on 127.0.0.1 starts fine and nobody can reach it.
  • port: the one the host gives you, in SERVER_PORT on SnowServers and often PORT elsewhere.
  • workers=2: two processes, so one slow request does not hold up the rest. Each is a full copy of the app in memory; two suit a 1 GB plan. With workers, the app has to be given as the string "app:app", not the object.
  • proxy_headers and forwarded_allow_ips: next section.

3. Behind HTTPS: the proxy headers

When HTTPS is handled in front of your app, uvicorn receives plain HTTP from the proxy. The proxy puts the real scheme and the visitor's address in X-Forwarded-Proto and X-Forwarded-For. uvicorn only believes those headers from the addresses in forwarded_allow_ips, which is 127.0.0.1 by default. When the proxy connects from another address, as it does from outside a container, nothing is believed: request.url says http://, url_for builds http:// links, and every visitor seems to come from the proxy.

forwarded_allow_ips="*" trusts the headers from anyone. That is right when every request comes through a proxy that sets them itself, and it is how the example above is meant for SnowServers, where the proxy overwrites both. Your server's own address and port also answer plain HTTP to anyone who has them, and a request sent there directly can carry made-up headers, so do not use the visitor address from X-Forwarded-For for anything that matters, such as allow-lists.

4. Secrets and settings

API keys and database passwords go in a .env file next to the app, never in the code or in Git; load_dotenv() in app.py reads it, and the values are in os.environ. Add .env to .gitignore.

FastAPI publishes interactive API documentation at /docs and /redoc. That is handy, and on a public site it is a map of every endpoint. If you do not want that, turn them off:

Python
app = FastAPI(docs_url=None, redoc_url=None, openapi_url=None)

5. Test it the way the server will run it

Terminal
python -m venv venv
source venv/bin/activate        # Windows: venv\Scripts\activate
pip install -r requirements.txt
SERVER_PORT=8000 python main.py

On Windows, set the port first (set SERVER_PORT=8000 in cmd, $env:SERVER_PORT=8000 in PowerShell). If http://localhost:8000 answers, the host will too.

Hosting it on SnowServers

  1. Choose a plan on the website plans page and pick A website and Python at checkout. Flurry, at 1 GB, runs most FastAPI apps.
  2. Upload app.py, main.py and requirements.txt in Files, or paste your GitHub repository into Git repository on the Startup tab. Put .env in by hand.
  3. main.py is picked as the start file on its own, ahead of the starter app.py. Your own app.py replaces the starter's.
  4. Restart from the console. It shows the packages installing and uvicorn starting its workers.
  5. On the Website tab, add your domain and the DNS records it shows. HTTPS follows on its own, usually within a few minutes.

WebSockets work through your domain. Uploads can be up to 25 MB each, and a request that sends nothing back for two minutes is cut off by the proxy, so long jobs belong in a background task that the client checks on. The website docs cover the rest.

Common problems

  • "Error loading ASGI app. Could not import module": the string in uvicorn.run does not match your files. "app:app" means the variable app in app.py; for src/api.py it would be "src.api:app".
  • "You must pass the application as an import string": workers was used with the app object. Pass the string.
  • "This site is not answering": uvicorn is on 127.0.0.1 or the wrong port. Use 0.0.0.0 and SERVER_PORT.
  • Links and redirects go to http://: forwarded_allow_ips does not include the proxy.
  • Out of memory: fewer workers, or a bigger plan. The Crashes tab shows memory over the last day.

Press / or Ctrl K on any page to search.