Django
IP address
user data
web development
Django tutorial

How do I get user IP address in Django?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Reading a client IP address in Django is easy on a local development server and tricky in production. The problem is not accessing request metadata. The problem is knowing which address is real when the request passed through Nginx, a load balancer, a CDN, or all three.

Start With request.META

Django exposes transport headers through request.META. The fields you normally inspect are:

  • 'REMOTE_ADDR'
  • 'HTTP_X_FORWARDED_FOR'
  • 'HTTP_X_REAL_IP'

A simple debugging view makes it obvious what your deployment is actually sending:

python
1from django.http import JsonResponse
2
3
4def show_ip_headers(request):
5    return JsonResponse(
6        {
7            "REMOTE_ADDR": request.META.get("REMOTE_ADDR"),
8            "HTTP_X_FORWARDED_FOR": request.META.get("HTTP_X_FORWARDED_FOR"),
9            "HTTP_X_REAL_IP": request.META.get("HTTP_X_REAL_IP"),
10        }
11    )

In a direct connection, REMOTE_ADDR is often the client IP. Behind a reverse proxy, it may only be the address of the last proxy hop.

Do Not Trust Forwarded Headers Automatically

This is the most important rule. A client can send X-Forwarded-For itself unless your proxy strips or rewrites that header. So the first useful question is not "which header contains an IP" but "which header is written by infrastructure I trust."

If your edge proxy is configured correctly, the first value in X-Forwarded-For is typically the original client address and later values are proxies added along the way.

A Safe Helper Function

You usually want one helper that validates the chosen address and keeps the policy in one place:

python
1from ipaddress import ip_address
2
3
4def get_client_ip(request, trust_x_forwarded_for=False):
5    if trust_x_forwarded_for:
6        forwarded = request.META.get("HTTP_X_FORWARDED_FOR", "")
7        if forwarded:
8            first = forwarded.split(",")[0].strip()
9            ip_address(first)
10            return first
11
12    remote_addr = request.META.get("REMOTE_ADDR")
13    if remote_addr:
14        ip_address(remote_addr)
15        return remote_addr
16
17    return None

Validation is worth doing. It prevents malformed or unexpected values from quietly flowing into logs, rate limits, or audit tables.

Configure the Proxy Layer Correctly

Application code only works if the proxy chain is consistent. A common Nginx setup looks like this:

nginx
1location / {
2    proxy_set_header Host $host;
3    proxy_set_header X-Real-IP $remote_addr;
4    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
5    proxy_set_header X-Forwarded-Proto $scheme;
6    proxy_pass http://django_upstream;
7}

With that configuration, Django receives both the direct upstream address and the forwarded client chain. Without this step, no Python helper can reconstruct information that never arrived.

Put the Logic in Middleware if Needed Everywhere

If many views, logging hooks, or permission checks need the client IP, middleware keeps the parsing logic centralized:

python
1class ClientIPMiddleware:
2    def __init__(self, get_response):
3        self.get_response = get_response
4
5    def __call__(self, request):
6        request.client_ip = get_client_ip(request, trust_x_forwarded_for=True)
7        return self.get_response(request)

That reduces duplicated header parsing across the codebase. It also makes it easier to change policy later if the deployment path changes.

Remember Privacy and Product Requirements

An IP address is operationally useful, but it is still sensitive data in many contexts. Ask why you need it:

  • abuse prevention
  • geo hints
  • fraud analysis
  • audit logs

If the goal is rate limiting, a raw IP may be justified. If the goal is rough analytics, a shortened or hashed form may be enough. Good engineering here is partly a product and compliance decision, not only a Django question.

Test in a Staging Topology

Many bugs appear because developers test only with runserver. Production requests may pass through several layers before Django sees them. A temporary staging endpoint that prints incoming headers is often the fastest way to verify exactly what your stack is forwarding.

Without that test, teams guess, then later discover that all rate limits were applied to the load balancer IP instead of the client.

Common Pitfalls

  • Treating HTTP_X_FORWARDED_FOR as trustworthy without controlling the proxy that sets it.
  • Assuming REMOTE_ADDR is always the browser IP in production.
  • Parsing IP headers separately in multiple views instead of centralizing the policy.
  • Forgetting to validate the chosen value before storing or using it.
  • Testing only on a local server and not on the real proxy chain.

Summary

  • Django exposes possible client IP data through request.META.
  • The correct address depends on which proxies you trust and how they forward headers.
  • 'REMOTE_ADDR is simple but often reflects only the last hop in proxied deployments.'
  • A small helper or middleware keeps IP extraction consistent and auditable.
  • Infrastructure configuration matters as much as the Django code.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.