@ -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
```
instead of serving it directly at the root of the domain.
### Why serve under a prefix?
The `root_path` is a mechanism provided by the ASGI specification (that FastAPI is built on, through Starlette).
This is common when:
The `root_path` is used to handle these specific cases.
* 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.
And it's also used internally when mounting sub-applications.
Hosting under a prefix keeps everything accessible under a single base URL (like `https://example.com`), simplifying proxy, SSL, and frontend configuration.
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`.
### 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`.
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.
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
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`.
```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"
```
Up to here, everything would work as normally.
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 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`.
Now the app's routing works perfectly - `/app` matches exactly as expected. 🎉
So, the frontend (that runs in the browser) would try to reach `/openapi.json` and wouldn't be able to get the OpenAPI schema.
But... something is still off...
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`.
### 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.
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:
```JSON hl_lines="4-8"
```
{
"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:
* Match routes correctly (`/app` inside the app).
* Generate proper URLs and redirects that include the prefix (`/api/v1/app`).
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? 🤔
### Providing the `root_path` { #providing-the-root-path }
As you may recall, there are three main components involved in serving your FastAPI application:
To achieve this, you can use the command line option `--root-path` like:
* **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`.
@ -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:
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 <ahref="http://127.0.0.1:8000"class="external-link"target="_blank">http://127.0.0.1:8000/app</a> 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": "",
}
```
This scope is not compliant with the ASGI specification.
But thanks to the `root_path` parameter, FastAPI corrects the scope to:
```
{
"path": "/api/v1/app",
"root_path": "/api/v1",
}
```
So, it won't expect to be accessed at `http://127.0.0.1:8000/api/v1/app`.
/// warning | Attention
Don't forget - this setup assumes your app runs behind a proxy that **keeps (adds)** the `/api/v1` prefix when forwarding requests.
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.:
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.
This allows a single FastAPI instance to handle requests under multiple prefixes, with `root_path` correctly set for each request.
## About proxies with a stripped path prefix { #about-proxies-with-a-stripped-path-prefix }
Run the server:
Keep in mind that a proxy with stripped path prefix is only one of the ways to configure it.
<divclass="termy">
```console
$ fastapi run main.py --forwarded-allow-ips="*"
<spanstyle="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
Probably in many cases the default will be that the proxy doesn't have a stripped path prefix.
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`.
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 }