python - How do I make FastAPI URLs include the proxied URL? - Stack Overflow

admin2025-04-16  2

I have a FastAPI application that is behind a NextJS reverse proxy. I'm using NextJS rewrites, which sets the x-forwarded-for header to the externally-visible hostname and port. The rewrite looks like this:

rewrites: async () => [
    {
        source: "/api/:slug*",
        destination: "http://backend:8000/:slug*"
    }
]

The whole lot is running in a docker stack (which is where hostnames like backend come from) and I end up with headers like this:

host: backend:8000
x-forwarded-host: localhost:3000

I'm then emailing a link from the FastAPI application. I construct the URL like this:

@app.get("/verify")
async def verify(token: str):
    ...

@app.post("/signup")
async def signup(body: SignupRequest, request: Request) -> str:
    user = add_user(body.username, body.email)
    token = user.get_signup_token()
    url = request.url_for("verify").include_query_params(token=token)
    email_verification(body.email, url)
    return ""

I've set up FastAPI with root_path="/api" so that the path is rewritten correctly.

The resulting url is http://backend:8000/api/verify. I want it to be http://localhost:3000/api/verify (ie to have the actual hosted URL rather than the intra-docker-stack URL).

I've tried adding a middleware like this:

@app.middleware("http")
async def rewrite_host_header(request: Request, call_next):
    if "x-forwarded-host" in request.headers:
        request.headers["host"] = request.headers["x-forwarded-host"]
    return await call_next(request)

but this doesn't seem to make a difference. I've also tried adding request.url = request.url.replace(host=request.headers["x-forwarded-for"]) but this also doesn't change the output of request.url_for(...).

How am I meant to configure this so that URLs are emitted with the right scheme, hostname and port?

Edit to add: I've tried also setting X-Forwarded-Proto, X-Forwarded-Port, X-Forwarded-Prefix and X-Forwarded-For, making requests directly to FastAPI using curl and so cutting out the NextJS step. None of it makes any difference to the URLs that url_for() emits.

I have a FastAPI application that is behind a NextJS reverse proxy. I'm using NextJS rewrites, which sets the x-forwarded-for header to the externally-visible hostname and port. The rewrite looks like this:

rewrites: async () => [
    {
        source: "/api/:slug*",
        destination: "http://backend:8000/:slug*"
    }
]

The whole lot is running in a docker stack (which is where hostnames like backend come from) and I end up with headers like this:

host: backend:8000
x-forwarded-host: localhost:3000

I'm then emailing a link from the FastAPI application. I construct the URL like this:

@app.get("/verify")
async def verify(token: str):
    ...

@app.post("/signup")
async def signup(body: SignupRequest, request: Request) -> str:
    user = add_user(body.username, body.email)
    token = user.get_signup_token()
    url = request.url_for("verify").include_query_params(token=token)
    email_verification(body.email, url)
    return ""

I've set up FastAPI with root_path="/api" so that the path is rewritten correctly.

The resulting url is http://backend:8000/api/verify. I want it to be http://localhost:3000/api/verify (ie to have the actual hosted URL rather than the intra-docker-stack URL).

I've tried adding a middleware like this:

@app.middleware("http")
async def rewrite_host_header(request: Request, call_next):
    if "x-forwarded-host" in request.headers:
        request.headers["host"] = request.headers["x-forwarded-host"]
    return await call_next(request)

but this doesn't seem to make a difference. I've also tried adding request.url = request.url.replace(host=request.headers["x-forwarded-for"]) but this also doesn't change the output of request.url_for(...).

How am I meant to configure this so that URLs are emitted with the right scheme, hostname and port?

Edit to add: I've tried also setting X-Forwarded-Proto, X-Forwarded-Port, X-Forwarded-Prefix and X-Forwarded-For, making requests directly to FastAPI using curl and so cutting out the NextJS step. None of it makes any difference to the URLs that url_for() emits.

Share Improve this question edited Feb 3 at 19:51 Chris 35.6k10 gold badges104 silver badges251 bronze badges asked Feb 2 at 19:54 TomTom 8,0425 gold badges48 silver badges89 bronze badges 2
  • You may find code excerpts in this and this that might prove helpful to you. – Chris Commented Feb 3 at 11:53
  • Yep, that's what I ended up doing, thanks. Added middleware to set host from x-forwarded-for. – Tom Commented Feb 3 at 20:43
Add a comment  | 

1 Answer 1

Reset to default 0

I think the issue is that Uvicorn (if you're using it) does not trust proxy headers by default unless specified.

According to the FastAPI documentation about deploying FastAPI on Docker behind a proxy, you need to enable proxy headers.

Additionally, Starlette provides information about Uvicorn middleware for handling proxy header. The Uvicorn GitHub code also provides insight into how proxy headers are processed.

转载请注明原文地址:http://anycun.com/QandA/1744788623a87637.html