From f23fba1eade3d1aca643dd59bcd152a51e2818f2 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Mon, 27 Oct 2025 22:35:56 +0100 Subject: [PATCH 1/3] Update the part of "Behind a proxy" section related to `root_path` --- docs/en/docs/advanced/behind-a-proxy.md | 371 +++++++++++++++--- docs_src/behind_a_proxy/tutorial005.py | 27 ++ .../test_behind_a_proxy/test_tutorial005.py | 58 +++ 3 files changed, 404 insertions(+), 52 deletions(-) create mode 100644 docs_src/behind_a_proxy/tutorial005.py create mode 100644 tests/test_tutorial/test_behind_a_proxy/test_tutorial005.py diff --git a/docs/en/docs/advanced/behind-a-proxy.md b/docs/en/docs/advanced/behind-a-proxy.md index 4d19d29e0..d51c532b1 100644 --- a/docs/en/docs/advanced/behind-a-proxy.md +++ b/docs/en/docs/advanced/behind-a-proxy.md @@ -97,19 +97,52 @@ These headers preserve information about the original request that would otherwi When **FastAPI CLI** is configured with `--forwarded-allow-ips`, it trusts these headers and uses them, for example to generate the correct URLs in redirects. -## Proxy with a stripped path prefix { #proxy-with-a-stripped-path-prefix } +## Serve the app under a path prefix -You could have a proxy that adds a path prefix to your application. +In production, you might want to serve your FastAPI application under a URL prefix, such as: -In these cases you can use `root_path` to configure your application. +``` +https://example.com/api/v1 +``` -The `root_path` is a mechanism provided by the ASGI specification (that FastAPI is built on, through Starlette). +instead of serving it directly at the root of the domain. -The `root_path` is used to handle these specific cases. +### Why serve under a prefix? -And it's also used internally when mounting sub-applications. +This is common when: -Having a proxy with a stripped path prefix, in this case, means that you could declare a path at `/app` in your code, but then, you add a layer on top (the proxy) that would put your **FastAPI** application under a path like `/api/v1`. +* You host several applications on the same host and port - for example, an API, a dashboard, and an admin panel, all behind the same domain. +Serving them under different prefixes (like `/api`, `/dashboard`, and `/admin`) helps you avoid setting up multiple domains or ports and prevents cross-origin (CORS) issues. +* You deploy multiple API versions (e.g. `/v1`, `/v2`) side by side to ensure backward compatibility while rolling out new features. + +Hosting under a prefix keeps everything accessible under a single base URL (like `https://example.com`), simplifying proxy, SSL, and frontend configuration. + +### Routing requests through a reverse proxy + +In these setups, you typically run your FastAPI app behind a reverse proxy such as Traefik, Nginx, Caddy, or an API Gateway. +The proxy is configured to route requests under a specific path prefix. + +For example, using a Traefik router: + +```TOML hl_lines="8" +[http] + + [http.routers] + + [http.routers.app-http] + entryPoints = ["http"] + service = "app" + rule = "PathPrefix(`/api/v1`)" + + [http.services] + + [http.services.app] + [http.services.app.loadBalancer] + [[http.services.app.loadBalancer.servers]] + url = "http://127.0.0.1:8000" +``` + +This router config tells Traefik to forward all requests starting with `/api/v1` to your FastAPI service. In this case, the original path `/app` would actually be served at `/api/v1/app`. @@ -117,15 +150,56 @@ Even though all your code is written assuming there's just `/app`. {* ../../docs_src/behind_a_proxy/tutorial001.py hl[6] *} -And the proxy would be **"stripping"** the **path prefix** on the fly before transmitting the request to the app server (probably Uvicorn via FastAPI CLI), keeping your application convinced that it is being served at `/app`, so that you don't have to update all your code to include the prefix `/api/v1`. +...but your app only knows routes like `/app` and has no idea about the `/api/v1` prefix. -Up to here, everything would work as normally. +As a result, the app won't be able to match the routes correctly, and clients will get `404 Not Found` errors 😱. -But then, when you open the integrated docs UI (the frontend), it would expect to get the OpenAPI schema at `/openapi.json`, instead of `/api/v1/openapi.json`. +### Stripping the prefix -So, the frontend (that runs in the browser) would try to reach `/openapi.json` and wouldn't be able to get the OpenAPI schema. +One simple way to solve the problem described above is to configure the proxy to strip the prefix before forwarding the request to the app. +For example, if the client requests `/api/v1/app`, the proxy forwards it as just `/app`. -Because we have a proxy with a path prefix of `/api/v1` for our app, the frontend needs to fetch the OpenAPI schema at `/api/v1/openapi.json`. +```TOML hl_lines="2-5 13" +[http] + [http.middlewares] + + [http.middlewares.api-stripprefix.stripPrefix] + prefixes = ["/api/v1"] + + [http.routers] + + [http.routers.app-http] + entryPoints = ["http"] + service = "app" + rule = "PathPrefix(`/api/v1`)" + middlewares = ["api-stripprefix"] + + [http.services] + + [http.services.app] + [http.services.app.loadBalancer] + [[http.services.app.loadBalancer.servers]] + url = "http://127.0.0.1:8000" +``` + +The proxy would be **"stripping"** the **path prefix** on the fly before transmitting the request to the app server (probably Uvicorn via FastAPI CLI), keeping your application convinced that it is being served at `/app`, so that you don't have to update all your code to include the prefix `/api/v1`. + +Now the app's routing works perfectly - `/app` matches exactly as expected. 🎉 + +But... something is still off... + +### When the app doesn't know it's running under a prefix + +While stripping the prefix by the proxy fixes incoming requests, the app still doesn't know that it's being served under `/api/v1`. +So any URLs generated inside the app - like those from `url_for()`, URL of `openapi.json` in docs, or redirects - will miss the prefix. +Clients will get links like `/app` instead of `/api/v1/app`, breaking navigation and documentation. + +Let's look closer at the problem with URL of `openapi.json` in docs. + +When you open the integrated docs UI (the frontend), it expects to get the OpenAPI schema at `/openapi.json`. +But, since your app is served under the `/api/v1` prefix, the correct URL of `openapi.json` would be `/api/v1/openapi.json`. + +So the frontend, running in the browser, will try to reach `/openapi.json` and fail to get the OpenAPI schema. ```mermaid graph LR @@ -144,28 +218,67 @@ The IP `0.0.0.0` is commonly used to mean that the program listens on all the IP /// -The docs UI would also need the OpenAPI schema to declare that this API `server` is located at `/api/v1` (behind the proxy). For example: +At this point, it becomes clear that we need to tell the app the path prefix on which it's running. -```JSON hl_lines="4-8" +Luckily, this problem isn't new - and the people who designed the ASGI specification have already thought about it. + +### Understanding `root_path` in ASGI + +ASGI defines two fields that make it possible for applications to know where they are mounted and still handle requests correctly: + +* `path` – the full path requested by the client (including the prefix) +* `root_path` – the mount point (the prefix itself) under which the app is served + +With this information, the application always knows both: + +* what the user actually requested (`path`), and +* where the app lives in the larger URL structure (`root_path`). + +For example, if the client requests: + +``` +/api/v1/app +``` + +the ASGI server should pass this to the app as: + +``` { - "openapi": "3.1.0", - // More stuff here - "servers": [ - { - "url": "/api/v1" - } - ], - "paths": { - // More stuff here - } + "path": "/api/v1/app", + "root_path": "/api/v1", + ... } ``` -In this example, the "Proxy" could be something like **Traefik**. And the server would be something like FastAPI CLI with **Uvicorn**, running your FastAPI application. +This allows the app to: -### Providing the `root_path` { #providing-the-root-path } +* Match routes correctly (`/app` inside the app). +* Generate proper URLs and redirects that include the prefix (`/api/v1/app`). -To achieve this, you can use the command line option `--root-path` like: +This is the elegant mechanism that makes it possible for ASGI applications - including FastAPI - to work smoothly behind reverse proxies or under nested paths without needing to rewrite routes manually. + +### Providing the `root_path` + +So, the ASGI scope needs to contain the correct `path` and `root_path`. +But... who is actually responsible for setting them? 🤔 + +As you may recall, there are three main components involved in serving your FastAPI application: + +* **Reverse proxy** (like Traefik or Nginx) - receives client requests first and passes them to the ASGI server. +* **ASGI server** (like Uvicorn or Hypercorn) - runs your FastAPI application and manages the ASGI lifecycle. +* **ASGI app** itself (the app you're building with FastAPI, your favorite framework) - handles routing, generates URLs, and processes requests. + +There are a few common ways these components can work together to ensure `root_path` is set correctly: + +1. The proxy strips the prefix, and the ASGI server (e.g., Uvicorn) adds it back and sets `root_path` in the ASGI `scope`. +2. The proxy keeps the prefix and forwards requests as-is, while the ASGI app is started with the correct `root_path` parameter. +3. The proxy keeps the prefix but also sends an `X-Forwarded-Prefix` header, and a middleware in the app uses that header to dynamically set `root_path`. + +Let's look at each of these approaches in detail. + +#### Uvicorn `--root-path` (proxy strips prefix) { #uvicorn-root-path-proxy-strips-prefix } + +If your proxy removes the prefix before forwarding requests, you should use the `--root-path` option of your ASGI server:
@@ -179,23 +292,28 @@ $ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 If you use Hypercorn, it also has the option `--root-path`. -/// note | Technical Details +Here's what happens in this setup: -The ASGI specification defines a `root_path` for this use case. +* The proxy sends `/app` to the ASGI server (Uvicorn). +* Uvicorn adds the prefix to `path` and sets `root_path` to `/api/v1` in the ASGI scope. +* Your FastAPI app receives: -And the `--root-path` command line option provides that `root_path`. +``` +{ + "path": "/api/v1/app", + "root_path": "/api/v1", +} +``` -/// +✅ This is fully compliant with the ASGI specification. -### Checking the current `root_path` { #checking-the-current-root-path } +FastAPI automatically respects `root_path` in the scope when matching routes or generating URLs. -You can get the current `root_path` used by your application for each request, it is part of the `scope` dictionary (that's part of the ASGI spec). - -Here we are including it in the message just for demonstration purposes. +Run the following example app: {* ../../docs_src/behind_a_proxy/tutorial001.py hl[8] *} -Then, if you start Uvicorn with: +with the command:
@@ -207,7 +325,7 @@ $ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
-The response would be something like: +The response will look something like this: ```JSON { @@ -216,38 +334,187 @@ The response would be something like: } ``` -### Setting the `root_path` in the FastAPI app { #setting-the-root-path-in-the-fastapi-app } +/// warning | Attention -Alternatively, if you don't have a way to provide a command line option like `--root-path` or equivalent, you can set the `root_path` parameter when creating your FastAPI app: +Don't forget - this setup assumes your proxy **removes** the `/api/v1` prefix before forwarding requests to the app. + +If the proxy **doesn't strip** the prefix, you will end up with duplicated paths like `/api/v1/api/v1/app`. + +And if you run the server **without a proxy at all**, your app will still handle requests to URLs like `/app`, +but URL generation, redirects, and the interactive docs will break due to the missing prefix configuration. + +/// + +/// tip + +This is the most straightforward and common approach. +You should probably use it unless you have a specific reason to do otherwise. + +/// + +Unfortunately, not all ASGI servers support a `--root-path` option or automatically adjust the `path` in the ASGI scope. +If your server doesn't, you can use one of the alternative approaches described below. + +#### Passing `root_path` as an argument to FastAPI (proxy keeps prefix) + +If the proxy keeps the prefix in the forwarded request: + +```TOML +[http] + + [http.routers] + + [http.routers.app-http] + entryPoints = ["http"] + service = "app" + rule = "PathPrefix(`/api/v1`)" + + [http.services] + + [http.services.app] + [http.services.app.loadBalancer] + [[http.services.app.loadBalancer.servers]] + url = "http://127.0.0.1:8000" +``` + +you can configure FastAPI like this: {* ../../docs_src/behind_a_proxy/tutorial002.py hl[3] *} -Passing the `root_path` to `FastAPI` would be the equivalent of passing the `--root-path` command line option to Uvicorn or Hypercorn. +In this setup: -### About `root_path` { #about-root-path } +The app receives requests with `/api/v1` included in the path. -Keep in mind that the server (Uvicorn) won't use that `root_path` for anything else than passing it to the app. +FastAPI uses the `root_path` parameter to adjust the ASGI scope so that routing, redirects, and URL generation work correctly. -But if you go with your browser to http://127.0.0.1:8000/app you will see the normal response: +Without the `root_path` parameter, the incoming scope from the ASGI server looks like this: -```JSON +``` { - "message": "Hello World", - "root_path": "/api/v1" + "path": "/api/v1/app", + "root_path": "", } ``` -So, it won't expect to be accessed at `http://127.0.0.1:8000/api/v1/app`. +This scope is not compliant with the ASGI specification. -Uvicorn will expect the proxy to access Uvicorn at `http://127.0.0.1:8000/app`, and then it would be the proxy's responsibility to add the extra `/api/v1` prefix on top. +But thanks to the `root_path` parameter, FastAPI corrects the scope to: -## About proxies with a stripped path prefix { #about-proxies-with-a-stripped-path-prefix } +``` +{ + "path": "/api/v1/app", + "root_path": "/api/v1", +} +``` -Keep in mind that a proxy with stripped path prefix is only one of the ways to configure it. +/// warning | Attention -Probably in many cases the default will be that the proxy doesn't have a stripped path prefix. +Don't forget - this setup assumes your app runs behind a proxy that **keeps (adds)** the `/api/v1` prefix when forwarding requests. -In a case like that (without a stripped path prefix), the proxy would listen on something like `https://myawesomeapp.com`, and then if the browser goes to `https://myawesomeapp.com/api/v1/app` and your server (e.g. Uvicorn) listens on `http://127.0.0.1:8000` the proxy (without a stripped path prefix) would access Uvicorn at the same path: `http://127.0.0.1:8000/api/v1/app`. +If the proxy **strips the prefix** or **doesn't add it at all**, your app might seem to work for some routes, but features like interactive docs, mounted sub-applications, and redirects will fail. + +If you encounter strange issues with this configuration, double-check your proxy settings. + +/// + +/// note + +Use this approach when your ASGI server doesn't support a `--root-path` option, or if you need to configure your reverse proxy to keep the prefix in the path. + +Otherwise, prefer using the `--root-path` approach [described above](#uvicorn-root-path-proxy-strips-prefix){.internal-link target=_blank}. + +/// + +#### Using `X-Forwarded-Prefix` header + +This is another common approach. It's the most flexible, but requires a more initial configuration. + +/// warning + +This is a more advanced approach. In most cases, you should use the `--root-path` option [described above](#uvicorn-root-path-proxy-strips-prefix){.internal-link target=_blank}. + +/// + +Imagine the prefix you use to serve the app might change over time. With the `--root-path` approach, you would need to update both the proxy configuration and the ASGI server command each time the prefix changes. + +Or, suppose you want to serve your app under multiple prefixes, such as `/api/v1` and `/backend/v1`. You would then need multiple instances of your app configured with different `--root-path` values - not ideal. + +There is a better solution for this! 💡 +Configure your reverse proxy to send the mount prefix in a header, e.g.: + +``` +X-Forwarded-Prefix: /api/v1 +``` + +Here is an example Traefik configuration: + +```TOML +[http] + + [http.routers] + + [http.routers.app-api-v1] + entryPoints = ["http"] + service = "app" + rule = "PathPrefix(`/api/v1`)" + middlewares = ["prefix-api-v1"] + + [http.routers.app-backend-v1] + entryPoints = ["http"] + service = "app" + rule = "PathPrefix(`/backend/v1`)" + middlewares = ["prefix-backend-v1"] + + [http.services] + + [http.services.app.loadBalancer] + [[http.services.app.loadBalancer.servers]] + url = "http://127.0.0.1:8000" + + [http.middlewares] + + [http.middlewares.prefix-api-v1.headers.customRequestHeaders] + X-Forwarded-Prefix = "/api/v1" + + [http.middlewares.prefix-backend-v1.headers.customRequestHeaders] + X-Forwarded-Prefix = "/backend/v1" +``` + +/// note + +`X-Forwarded-Prefix` is not a part of any standard, but it is widely recognized for informing an app of its URL path prefix. + +/// + +You can then use middleware to read this header and dynamically set the correct `root_path`: + +{* ../../docs_src/behind_a_proxy/tutorial005.py *} + +This allows a single FastAPI instance to handle requests under multiple prefixes, with `root_path` correctly set for each request. + +Run the server: + +
+ +```console +$ fastapi run main.py --forwarded-allow-ips="*" + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Test with `curl`: + +``` +curl -H "x-forwarded-prefix: /api/v1" http://127.0.0.1:8000/api/v1/app +# {"message":"Hello World","root_path":"/api/v1"} + +curl -H "x-forwarded-prefix: /backend/v1" http://127.0.0.1:8000/backend/v1/app +# {"message":"Hello World","root_path":"/backend/v1"} +``` + +The same FastAPI instance now handles requests for multiple prefixes, using the correct `root_path` each time. ## Testing locally with Traefik { #testing-locally-with-traefik } diff --git a/docs_src/behind_a_proxy/tutorial005.py b/docs_src/behind_a_proxy/tutorial005.py new file mode 100644 index 000000000..6548d4d7b --- /dev/null +++ b/docs_src/behind_a_proxy/tutorial005.py @@ -0,0 +1,27 @@ +from fastapi import FastAPI, Request +from starlette.types import ASGIApp, Receive, Scope, Send + + +class ForwardedPrefixMiddleware: + def __init__(self, app: ASGIApp): + self.app = app + + async def __call__(self, scope: Scope, receive: Receive, send: Send): + if scope["type"] in ("http", "websocket"): + scope_headers: list[tuple[bytes, bytes]] = scope.get("headers", []) + headers = { + k.decode("latin-1"): v.decode("latin-1") for k, v in scope_headers + } + prefix = headers.get("x-forwarded-prefix", "").rstrip("/") + if prefix: + scope["root_path"] = prefix + await self.app(scope, receive, send) + + +app = FastAPI() +app.add_middleware(ForwardedPrefixMiddleware) + + +@app.get("/app") +def read_main(request: Request): + return {"message": "Hello World", "root_path": request.scope.get("root_path")} diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial005.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial005.py new file mode 100644 index 000000000..1bc8504d0 --- /dev/null +++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial005.py @@ -0,0 +1,58 @@ +import pytest +from fastapi.testclient import TestClient + +from docs_src.behind_a_proxy.tutorial005 import app + +client = TestClient(app) + + +@pytest.mark.parametrize( + "root_path, requested_path, expected_root_path", + [ + ("/api/v1", "/api/v1/app", "/api/v1"), + ("/backend/v1", "/backend/v1/app", "/backend/v1"), + ("/backend/v1/", "/backend/v1/app", "/backend/v1"), + (None, "/app", ""), + ], +) +def test_forwarded_prefix_middleware( + root_path: str, + requested_path: str, + expected_root_path: str, +): + client = TestClient(app) + headers = {} + if root_path: + headers["x-forwarded-prefix"] = root_path + response = client.get(requested_path, headers=headers) + assert response.status_code == 200 + assert response.json()["root_path"] == expected_root_path + + +def test_openapi_servers(): + client = TestClient(app) + headers = {"x-forwarded-prefix": "/api/v1"} + response = client.get("/api/v1/openapi.json", headers=headers) + assert response.status_code == 200 + openapi_data = response.json() + assert "servers" in openapi_data + assert openapi_data["servers"] == [{"url": "/api/v1"}] + + +@pytest.mark.parametrize( + "root_path, requested_path, expected_openapi_url", + [ + ("/api/v1", "/api/v1/docs", "/api/v1/openapi.json"), + (None, "/docs", "/openapi.json"), + ], +) +def test_swagger_docs_openapi_url( + root_path: str, requested_path: str, expected_openapi_url: str +): + client = TestClient(app) + headers = {} + if root_path: + headers["x-forwarded-prefix"] = root_path + response = client.get(requested_path, headers=headers) + assert response.status_code == 200 + assert expected_openapi_url in response.text From c03fa4a65ae98b7f385235dfcb5ed42820e7c9b6 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Sun, 9 Nov 2025 18:00:35 +0100 Subject: [PATCH 2/3] Some fixes and improvements --- docs/en/docs/advanced/behind-a-proxy.md | 415 ++++++++++-------- .../img/tutorial/behind-a-proxy/image01.png | Bin 29123 -> 29624 bytes docs_src/behind_a_proxy/tutorial001.py | 6 +- docs_src/behind_a_proxy/tutorial002.py | 6 +- docs_src/behind_a_proxy/tutorial005.py | 6 +- .../test_behind_a_proxy/test_tutorial001.py | 8 +- .../test_behind_a_proxy/test_tutorial002.py | 8 +- .../test_behind_a_proxy/test_tutorial005.py | 16 +- 8 files changed, 266 insertions(+), 199 deletions(-) diff --git a/docs/en/docs/advanced/behind-a-proxy.md b/docs/en/docs/advanced/behind-a-proxy.md index d51c532b1..ef962d542 100644 --- a/docs/en/docs/advanced/behind-a-proxy.md +++ b/docs/en/docs/advanced/behind-a-proxy.md @@ -40,6 +40,15 @@ $ fastapi run --forwarded-allow-ips="*"
+/// note + +The default value for the `--forwarded-allow-ips` option is `127.0.0.1`. + +This means that your **server** will trust a **proxy** running on the same host and will accept headers added by that **proxy**. + +/// + + ### Redirects with HTTPS { #redirects-with-https } For example, let's say you define a *path operation* `/items/`: @@ -64,7 +73,7 @@ If you want to learn more about HTTPS, check the guide [About HTTPS](../deployme /// -### How Proxy Forwarded Headers Work +### How Proxy Forwarded Headers Work { #how-proxy-forwarded-headers-work } Here's a visual representation of how the **proxy** adds forwarded headers between the client and the **application server**: @@ -97,7 +106,97 @@ These headers preserve information about the original request that would otherwi When **FastAPI CLI** is configured with `--forwarded-allow-ips`, it trusts these headers and uses them, for example to generate the correct URLs in redirects. -## Serve the app under a path prefix + + +## Testing locally with Traefik { #testing-locally-with-traefik } + +You can easily run the configuration with reverse proxy and ASGI application behind it locally using Traefik. + +Download Traefik, it's a single binary, you can extract the compressed file and run it directly from the terminal. + +Then create a file `traefik.toml` with: + +```TOML hl_lines="3" +[entryPoints] + [entryPoints.http] + address = ":9999" + +[providers] + [providers.file] + filename = "routes.toml" +``` + +This tells Traefik to listen on port 9999 and to use another file `routes.toml`. + +/// tip + +We are using port 9999 instead of the standard HTTP port 80 so that you don't have to run it with admin (`sudo`) privileges. + +/// + +Now create that other file `routes.toml`: + +```TOML hl_lines="8 15" +[http] + + [http.routers] + + [http.routers.app-http] + entryPoints = ["http"] + service = "app" + rule = "PathPrefix(`/`)" + + [http.services] + + [http.services.app] + [http.services.app.loadBalancer] + [[http.services.app.loadBalancer.servers]] + url = "http://127.0.0.1:8000" +``` + +This file configures Traefik to forward all requests (``rule = "PathPrefix(`/`)"``) to your Uvicorn running on `http://127.0.0.1:8000`. + +Now start Traefik: + +
+ +```console +$ ./traefik --configFile=traefik.toml + +INFO[0000] Configuration loaded from file: /home/user/awesomeapi/traefik.toml +``` + +
+ +And now let's start our app: + +{* ../../docs_src/behind_a_proxy/tutorial001_01.py *} + + +
+ +```console +$ fastapi run main.py --forwarded-allow-ips="*" + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +/// note + +`--forwarded-allow-ips="*"` option is redundant in this case since Uvicorn trusts **proxy** running on the same host by-default. + +/// + +Open `http://127.0.0.1:9999/items` in your browser. +Your request will be redirected by FastAPI to `http://127.0.0.1:9999/items/` and you will see: + +```json +["plumbus","portal gun"] +``` + +## Serve the app under a path prefix { #serve-the-app-under-a-path-prefix } In production, you might want to serve your FastAPI application under a URL prefix, such as: @@ -107,7 +206,7 @@ https://example.com/api/v1 instead of serving it directly at the root of the domain. -### Why serve under a prefix? +### Why serve under a prefix? { #why-serve-under-a-prefix } This is common when: @@ -117,9 +216,9 @@ Serving them under different prefixes (like `/api`, `/dashboard`, and `/admin`) Hosting under a prefix keeps everything accessible under a single base URL (like `https://example.com`), simplifying proxy, SSL, and frontend configuration. -### Routing requests through a reverse proxy +### Routing requests through a reverse proxy { #routing-requests-through-a-reverse-proxy } -In these setups, you typically run your FastAPI app behind a reverse proxy such as Traefik, Nginx, Caddy, or an API Gateway. +In these setups, you typically run your FastAPI app behind the same reverse proxy such as Traefik, Nginx, Caddy, or an API Gateway. The proxy is configured to route requests under a specific path prefix. For example, using a Traefik router: @@ -144,20 +243,39 @@ For example, using a Traefik router: This router config tells Traefik to forward all requests starting with `/api/v1` to your FastAPI service. -In this case, the original path `/app` would actually be served at `/api/v1/app`. +In this case, the original path `/items/` would actually be served at `/api/v1/items/`. -Even though all your code is written assuming there's just `/app`. +Even though all your code is written assuming there's just `/items/`. -{* ../../docs_src/behind_a_proxy/tutorial001.py hl[6] *} +...but your app only knows routes like `/items/` and has no idea about the `/api/v1` prefix. -...but your app only knows routes like `/app` and has no idea about the `/api/v1` prefix. +{* ../../docs_src/behind_a_proxy/tutorial001_01.py hl[6] *} As a result, the app won't be able to match the routes correctly, and clients will get `404 Not Found` errors 😱. -### Stripping the prefix + +```mermaid +sequenceDiagram + participant U as User + participant T as Traefik Proxy on 0.0.0.0:9999 + participant F as FastAPI App on 127.0.0.1:8000 + + U->>T: GET 'http://127.0.0.1:9999/api/v1/items/' + T->>F: Forwards request to 'http://127.0.0.1:8000/api/v1/items/' + F-->>T: 404 Not Found (FastAPI app only knows '/items/', not '/api/v1/items/') + T-->>U: 404 Not Found +``` + +/// tip + +The IP `0.0.0.0` is commonly used to mean that the program listens on all the IPs available in that machine/server. + +/// + +### Stripping the prefix { #stripping-the-prefix } One simple way to solve the problem described above is to configure the proxy to strip the prefix before forwarding the request to the app. -For example, if the client requests `/api/v1/app`, the proxy forwards it as just `/app`. +For example, if the client requests `/api/v1/items/`, the proxy forwards it as just `/items/`. ```TOML hl_lines="2-5 13" [http] @@ -182,17 +300,29 @@ For example, if the client requests `/api/v1/app`, the proxy forwards it as just url = "http://127.0.0.1:8000" ``` -The proxy would be **"stripping"** the **path prefix** on the fly before transmitting the request to the app server (probably Uvicorn via FastAPI CLI), keeping your application convinced that it is being served at `/app`, so that you don't have to update all your code to include the prefix `/api/v1`. +The proxy would be **"stripping"** the **path prefix** on the fly before transmitting the request to the app server (probably Uvicorn via FastAPI CLI), keeping your application convinced that it is being served at `/items/`, so that you don't have to update all your code to include the prefix `/api/v1`. -Now the app's routing works perfectly - `/app` matches exactly as expected. 🎉 +Now the app's **routing** works perfectly - `/items/` matches exactly as expected. 🎉 + +```mermaid +sequenceDiagram + participant U as User + participant T as Traefik Proxy on 0.0.0.0:9999 + participant F as FastAPI App on 127.0.0.1:8000 + + U->>T: GET 'http://127.0.0.1:9999/api/v1/items/' + T->>F: Forwards request to 'http://127.0.0.1:8000/items/' (prefix '/api/v1' is stripped) + F-->>T: 200 OK + T-->>U: 200 OK +``` But... something is still off... -### When the app doesn't know it's running under a prefix +### When the app doesn't know it's running under a prefix { #when-the-app-doesnt-know-its-running-under-a-prefix } While stripping the prefix by the proxy fixes incoming requests, the app still doesn't know that it's being served under `/api/v1`. So any URLs generated inside the app - like those from `url_for()`, URL of `openapi.json` in docs, or redirects - will miss the prefix. -Clients will get links like `/app` instead of `/api/v1/app`, breaking navigation and documentation. +Clients will get links like `/items/` instead of `/api/v1/items/`, breaking navigation and documentation. Let's look closer at the problem with URL of `openapi.json` in docs. @@ -202,27 +332,28 @@ But, since your app is served under the `/api/v1` prefix, the correct URL of `op So the frontend, running in the browser, will try to reach `/openapi.json` and fail to get the OpenAPI schema. ```mermaid -graph LR +sequenceDiagram + participant U as User + participant T as Traefik Proxy on 0.0.0.0:9999 + participant F as FastAPI App on 127.0.0.1:8000 -browser("Browser") -proxy["Proxy on http://0.0.0.0:9999/api/v1/app"] -server["Server on http://127.0.0.1:8000/app"] + U->>T: GET 'http://128.0.0.1:9999/api/v1/docs' + T->>F: Forwards to 'http://127.0.0.1:8000/docs' (prefix stripped) + F-->>T: 200 OK (Swagger UI page content) + T-->>U: 200 OK -browser --> proxy -proxy --> server + Note over U: Swagger UI page automatically requests OpenAPI schema + U->>T: (Browser) GET 'http://127.0.0.1:9999/openapi.json' + T-->>U: 404 Not Found (no matching router for this path) ``` -/// tip + -The IP `0.0.0.0` is commonly used to mean that the program listens on all the IPs available in that machine/server. - -/// - -At this point, it becomes clear that we need to tell the app the path prefix on which it's running. +At this point, it becomes clear that we need to tell the app the path prefix on which it's running. So that it could use this prefix to create working URLs. Luckily, this problem isn't new - and the people who designed the ASGI specification have already thought about it. -### Understanding `root_path` in ASGI +### Understanding `root_path` in ASGI { #understanding-root-path-in-asgi } ASGI defines two fields that make it possible for applications to know where they are mounted and still handle requests correctly: @@ -237,14 +368,14 @@ With this information, the application always knows both: For example, if the client requests: ``` -/api/v1/app +/api/v1/items/ ``` the ASGI server should pass this to the app as: ``` { - "path": "/api/v1/app", + "path": "/api/v1/items/", "root_path": "/api/v1", ... } @@ -252,12 +383,12 @@ the ASGI server should pass this to the app as: This allows the app to: -* Match routes correctly (`/app` inside the app). -* Generate proper URLs and redirects that include the prefix (`/api/v1/app`). +* Match routes correctly (`/items/` inside the app). +* Generate proper URLs and redirects that include the prefix (`/api/v1/items/`). This is the elegant mechanism that makes it possible for ASGI applications - including FastAPI - to work smoothly behind reverse proxies or under nested paths without needing to rewrite routes manually. -### Providing the `root_path` +### Providing the `root_path` { #providing-the-root-path } So, the ASGI scope needs to contain the correct `path` and `root_path`. But... who is actually responsible for setting them? 🤔 @@ -278,7 +409,11 @@ Let's look at each of these approaches in detail. #### Uvicorn `--root-path` (proxy strips prefix) { #uvicorn-root-path-proxy-strips-prefix } -If your proxy removes the prefix before forwarding requests, you should use the `--root-path` option of your ASGI server: +Let's now use the following app: + +{* ../../docs_src/behind_a_proxy/tutorial001.py *} + +If your proxy removes the prefix before forwarding requests (like in Traefik configuration from [Stripping the prefix](#stripping-the-prefix){.internal-link target=_blank}), you should use the `--root-path` option of your ASGI server:
@@ -294,8 +429,9 @@ If you use Hypercorn, it also has the option `--root-path`. Here's what happens in this setup: -* The proxy sends `/app` to the ASGI server (Uvicorn). -* Uvicorn adds the prefix to `path` and sets `root_path` to `/api/v1` in the ASGI scope. +* User requests `http://127.0.0.1:9999/api/v1/app` +* The proxy removes the `/api/v1` prefix and sends `/app` to the ASGI server (Uvicorn). +* Uvicorn adds the `/api/v1` prefix to `path` and sets `root_path` to `/api/v1` in the ASGI scope. * Your FastAPI app receives: ``` @@ -309,27 +445,14 @@ Here's what happens in this setup: FastAPI automatically respects `root_path` in the scope when matching routes or generating URLs. -Run the following example app: - -{* ../../docs_src/behind_a_proxy/tutorial001.py hl[8] *} - -with the command: - -
- -```console -$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
+Open your browser and go to http://127.0.0.1:9999/api/v1/app The response will look something like this: ```JSON { "message": "Hello World", + "path": "/api/v1/app", "root_path": "/api/v1" } ``` @@ -355,7 +478,7 @@ You should probably use it unless you have a specific reason to do otherwise. Unfortunately, not all ASGI servers support a `--root-path` option or automatically adjust the `path` in the ASGI scope. If your server doesn't, you can use one of the alternative approaches described below. -#### Passing `root_path` as an argument to FastAPI (proxy keeps prefix) +#### Passing `root_path` as an argument to FastAPI (proxy keeps prefix) { #passing-root-path-as-an-argument-to-fastapi-proxy-keeps-prefix } If the proxy keeps the prefix in the forwarded request: @@ -377,15 +500,30 @@ If the proxy keeps the prefix in the forwarded request: url = "http://127.0.0.1:8000" ``` +(don't forget to restart Traefik to apply new config) + you can configure FastAPI like this: {* ../../docs_src/behind_a_proxy/tutorial002.py hl[3] *} +And run your ASGI server **without** `--root-path` option: + +
+ +```console +$ fastapi run main.py --forwarded-allow-ips="*" + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ In this setup: -The app receives requests with `/api/v1` included in the path. - -FastAPI uses the `root_path` parameter to adjust the ASGI scope so that routing, redirects, and URL generation work correctly. +* User requests `http://127.0.0.1:9999/api/v1/app` +* The proxy sends `/api/v1/app` to the ASGI server (Uvicorn) +* ASGI server passes `/api/v1/app` to your app +* FastAPI uses the `root_path` parameter to adjust the ASGI scope so that routing, redirects, and URL generation work correctly. Without the `root_path` parameter, the incoming scope from the ASGI server looks like this: @@ -407,6 +545,18 @@ But thanks to the `root_path` parameter, FastAPI corrects the scope to: } ``` +Open your browser and go to http://127.0.0.1:9999/api/v1/app + +The response will look something like this: + +```JSON +{ + "message": "Hello World", + "path": "/api/v1/app", + "root_path": "/api/v1" +} +``` + /// warning | Attention Don't forget - this setup assumes your app runs behind a proxy that **keeps (adds)** the `/api/v1` prefix when forwarding requests. @@ -425,9 +575,9 @@ Otherwise, prefer using the `--root-path` approach [described above](#uvicorn-ro /// -#### Using `X-Forwarded-Prefix` header +#### Using `X-Forwarded-Prefix` header { #using-x-forwarded-prefix-header } -This is another common approach. It's the most flexible, but requires a more initial configuration. +This is another common approach. It's the most flexible, but requires more complex initial configuration. /// warning @@ -439,8 +589,9 @@ Imagine the prefix you use to serve the app might change over time. With the `-- Or, suppose you want to serve your app under multiple prefixes, such as `/api/v1` and `/backend/v1`. You would then need multiple instances of your app configured with different `--root-path` values - not ideal. -There is a better solution for this! 💡 -Configure your reverse proxy to send the mount prefix in a header, e.g.: +There is a better solution for this! + +💡 Configure your reverse proxy to send the mount prefix in a header, e.g.: ``` X-Forwarded-Prefix: /api/v1 @@ -488,11 +639,11 @@ Here is an example Traefik configuration: You can then use middleware to read this header and dynamically set the correct `root_path`: -{* ../../docs_src/behind_a_proxy/tutorial005.py *} +{* ../../docs_src/behind_a_proxy/tutorial005.py hl[1:22] *} This allows a single FastAPI instance to handle requests under multiple prefixes, with `root_path` correctly set for each request. -Run the server: +Run the server without `--root-path` option:
@@ -504,152 +655,40 @@ $ fastapi run main.py --forwarded-allow-ips="*"
-Test with `curl`: +Open your browser and go to http://127.0.0.1:9999/api/v1/app -``` -curl -H "x-forwarded-prefix: /api/v1" http://127.0.0.1:8000/api/v1/app -# {"message":"Hello World","root_path":"/api/v1"} - -curl -H "x-forwarded-prefix: /backend/v1" http://127.0.0.1:8000/backend/v1/app -# {"message":"Hello World","root_path":"/backend/v1"} -``` - -The same FastAPI instance now handles requests for multiple prefixes, using the correct `root_path` each time. - -## Testing locally with Traefik { #testing-locally-with-traefik } - -You can easily run the experiment locally with a stripped path prefix using Traefik. - -Download Traefik, it's a single binary, you can extract the compressed file and run it directly from the terminal. - -Then create a file `traefik.toml` with: - -```TOML hl_lines="3" -[entryPoints] - [entryPoints.http] - address = ":9999" - -[providers] - [providers.file] - filename = "routes.toml" -``` - -This tells Traefik to listen on port 9999 and to use another file `routes.toml`. - -/// tip - -We are using port 9999 instead of the standard HTTP port 80 so that you don't have to run it with admin (`sudo`) privileges. - -/// - -Now create that other file `routes.toml`: - -```TOML hl_lines="5 12 20" -[http] - [http.middlewares] - - [http.middlewares.api-stripprefix.stripPrefix] - prefixes = ["/api/v1"] - - [http.routers] - - [http.routers.app-http] - entryPoints = ["http"] - service = "app" - rule = "PathPrefix(`/api/v1`)" - middlewares = ["api-stripprefix"] - - [http.services] - - [http.services.app] - [http.services.app.loadBalancer] - [[http.services.app.loadBalancer.servers]] - url = "http://127.0.0.1:8000" -``` - -This file configures Traefik to use the path prefix `/api/v1`. - -And then Traefik will redirect its requests to your Uvicorn running on `http://127.0.0.1:8000`. - -Now start Traefik: - -
- -```console -$ ./traefik --configFile=traefik.toml - -INFO[0000] Configuration loaded from file: /home/user/awesomeapi/traefik.toml -``` - -
- -And now start your app, using the `--root-path` option: - -
- -```console -$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
- -### Check the responses { #check-the-responses } - -Now, if you go to the URL with the port for Uvicorn: http://127.0.0.1:8000/app, you will see the normal response: - -```JSON +```json { "message": "Hello World", + "path": "/api/v1/app", "root_path": "/api/v1" } ``` -/// tip +Now, go to http://127.0.0.1:9999/backend/v1/app -Notice that even though you are accessing it at `http://127.0.0.1:8000/app` it shows the `root_path` of `/api/v1`, taken from the option `--root-path`. - -/// - -And now open the URL with the port for Traefik, including the path prefix: http://127.0.0.1:9999/api/v1/app. - -We get the same response: - -```JSON +```json { "message": "Hello World", - "root_path": "/api/v1" + "path": "/backend/v1/app", + "root_path": "/backend/v1" } ``` -but this time at the URL with the prefix path provided by the proxy: `/api/v1`. +The same FastAPI instance now handles requests for multiple prefixes, using the correct `root_path` each time. 🎉 -Of course, the idea here is that everyone would access the app through the proxy, so the version with the path prefix `/api/v1` is the "correct" one. +#### Important note { #important-note } -And the version without the path prefix (`http://127.0.0.1:8000/app`), provided by Uvicorn directly, would be exclusively for the _proxy_ (Traefik) to access it. +Of course, the idea here is that everyone would access the app through the proxy (`http://192.168.0.1:9999` in our configuration), and the the URLs requested by user would contain the path prefix `/api/v1`. -That demonstrates how the Proxy (Traefik) uses the path prefix and how the server (Uvicorn) uses the `root_path` from the option `--root-path`. +And URLs without the path prefix (`http://127.0.0.1:8000/app`), provided by Uvicorn directly, would be exclusively for the _proxy_ (Traefik) to access it. -### Check the docs UI { #check-the-docs-ui } +It's quite common mistake that people configure the ASGI server with `--root-path` option and then attempt to access it directly (without reverse proxy). -But here's the fun part. ✨ +When `root_path` is configured (using any of the methods described above), always make sure that: -The "official" way to access the app would be through the proxy with the path prefix that we defined. So, as we would expect, if you try the docs UI served by Uvicorn directly, without the path prefix in the URL, it won't work, because it expects to be accessed through the proxy. - -You can check it at http://127.0.0.1:8000/docs: - - - -But if we access the docs UI at the "official" URL using the proxy with port `9999`, at `/api/v1/docs`, it works correctly! 🎉 - -You can check it at http://127.0.0.1:9999/api/v1/docs: - - - -Right as we wanted it. ✔️ - -This is because FastAPI uses this `root_path` to create the default `server` in OpenAPI with the URL provided by `root_path`. +* ✅ You are accessing your server via revers proxy +* ✅ URL includes prefix ## Additional servers { #additional-servers } diff --git a/docs/en/docs/img/tutorial/behind-a-proxy/image01.png b/docs/en/docs/img/tutorial/behind-a-proxy/image01.png index 8012031401c8365f9ff01c92479e05854bc8bf00..17216e302597eabb80a18b05b0a801b6dda45643 100644 GIT binary patch literal 29624 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 Date: Sun, 9 Nov 2025 17:12:04 +0000 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20for?= =?UTF-8?q?mat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py index 770caaa87..4e3ad8381 100644 --- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py +++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py @@ -11,7 +11,7 @@ def test_main(): assert response.json() == { "message": "Hello World", "path": "/api/v1/app", - "root_path": "/api/v1" + "root_path": "/api/v1", }