Learning Record learning from practice

· web

Getting the Client's IP in FastAPI/Flask, and Why X-Real-IP Isn't There Locally

Three things that get confused together whenever “how do I get the client’s IP” comes up: which attribute to read, why proxy headers exist at all, and what the browser DevTools “Remote Address” field actually means.

Client IP in FastAPI

FastAPI’s Request doesn’t have remote_addr (that’s Flask). The client address is on request.client:

from fastapi import FastAPI, Request

app = FastAPI()

@app.get("/test")
async def test(request: Request):
    return {
        "ip": request.client.host,
        "port": request.client.port,
    }
Attribute Meaning
request.client.host IP of the socket that connected to Uvicorn
request.client.port Its ephemeral source port
request.headers.get("X-Forwarded-For") Only set if something in front of Uvicorn adds it

Client IP in Flask

The Flask equivalent is request.remote_addr:

from flask import request

@app.route("/test")
def test():
    return {
        "remote_addr": request.remote_addr,
        "x_real_ip": request.headers.get("X-Real-IP"),
        "x_forwarded_for": request.headers.get("X-Forwarded-For"),
    }

Why X-Real-IP/X-Forwarded-For are empty locally

X-Real-IP and X-Forwarded-For are request headers, but the browser never sends them — a reverse proxy adds them when it forwards the request:

browser ──request──> Nginx ──adds X-Real-IP──> Flask/Uvicorn
              ↑                     ↑
     browser doesn't send      Nginx inserts the
     this header               real client IP here

Run the app directly (uvicorn main:app, no Nginx in front), and request.client.host/request.remote_addr already is the real client IP — there’s no proxy to have stripped or rewritten it, so the X-Forwarded-For/X-Real-IP headers simply don’t exist yet. That’s also why they’re invisible in browser DevTools: DevTools only shows what the browser sent, and these headers are added downstream by the proxy, not by the browser.

“Remote Address” in DevTools isn’t the client’s IP

The Remote Address field in the Network tab is the address the browser connected to — the server (or the nearest proxy/CDN in front of it) — not the browser’s own address:

browser (client) ──request──> server
   your IP                    Remote Address ← what DevTools shows

If you need the server’s own host/port inside a handler, that comes from the request line, not from anything client-supplied:

# FastAPI
request.url.hostname, request.url.port

# Flask
request.host        # "127.0.0.1:5000"
request.host_url     # "http://127.0.0.1:5000/"

Quick reference

Goal FastAPI Flask
Client IP, no proxy request.client.host request.remote_addr
Client IP, behind Nginx request.headers["X-Forwarded-For"] request.headers.get("X-Forwarded-For")
Server’s own address request.url.hostname request.host