381

How do I get user's IP in Django?

I have a view like this:

# Create your views
from django.contrib.gis.utils import GeoIP
from django.template import  RequestContext
from django.shortcuts import render_to_response

def home(request):
  g = GeoIP()
  client_ip = request.META['REMOTE_ADDR']
  lat,long = g.lat_lon(client_ip)
  return render_to_response('home_page_tmp.html',locals())

But I get this error:

KeyError at /mypage/
    'REMOTE_ADDR'
    Request Method: GET
    Request URL:    http://mywebsite.example/mypage/
    Django Version: 1.2.4
    Exception Type: KeyError
    Exception Value:
    'REMOTE_ADDR'
    Exception Location: /mysite/homepage/views.py in home, line 9
    Python Executable:  /usr/bin/python
    Python Version: 2.6.6
    Python Path:    ['/mysite', '/usr/local/lib/python2.6/dist-packages/flup-1.0.2-py2.6.egg', '/usr/lib/python2.6', '/usr/lib/python2.6/plat-linux2', '/usr/lib/python2.6/lib-tk', '/usr/lib/python2.6/lib-old', '/usr/lib/python2.6/lib-dynload', '/usr/local/lib/python2.6/dist-packages', '/usr/lib/python2.6/dist-packages', '/usr/lib/pymodules/python2.6']
    Server time:    Sun, 2 Jan 2011 20:42:50 -0600
Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
avatar
  • 12,087
  • 17
  • 66
  • 82
  • 2
    Try dumping request.META.keys() – Martin v. Löwis Jan 03 '11 at 03:07
  • 3
    ['HTTP_COOKIE', 'SCRIPT_NAME', 'REQUEST_METHOD', 'PATH_INFO', 'SERVER_PROTOCOL', 'QUERY_STRING', 'CONTENT_LENGTH', 'HTTP_ACCEPT_CHARSET', 'HTTP_USER_AGENT', 'HTTP_CONNECTION', 'SERVER_NAME', 'wsgi.url_scheme', 'SERVER_PORT', 'wsgi.input', 'HTTP_HOST', 'wsgi.multithread', 'HTTP_CACHE_CONTROL', 'HTTP_ACCEPT', 'wsgi.version', 'wsgi.run_once', 'wsgi.errors', 'wsgi.multiprocess', 'HTTP_ACCEPT_LANGUAGE', 'CONTENT_TYPE', 'CSRF_COOKIE', 'HTTP_ACCEPT_ENCODING'] – avatar Jan 03 '11 at 14:55
  • 3
    Thank you for this great question. My fastcgi was not passing the REMOTE_ADDR meta key. I added the line below in the nginx.conf and fixed the problem: fastcgi_param REMOTE_ADDR $remote_addr; – avatar Jan 03 '11 at 15:29

14 Answers14

576
def get_client_ip(request):
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        ip = x_forwarded_for.split(',')[0]
    else:
        ip = request.META.get('REMOTE_ADDR')
    return ip

Make sure you have reverse proxy (if any) configured correctly (e.g. mod_rpaf installed for Apache).

Note: the above uses the first item in X-Forwarded-For, but you might want to use the last item (e.g., in the case of Heroku: Get client's real IP address on Heroku)

And then just pass the request as argument to it;

get_client_ip(request)

Django documentation for HttpRequest.META

John Smith
  • 35
  • 1
  • 7
yanchenko
  • 56,576
  • 33
  • 147
  • 165
  • 1
    I'm using django + nginx + fastcgi – avatar Jan 03 '11 at 05:30
  • 1
    Still, log the contents of request.META and tweak your server config if neither `'HTTP_X_FORWARDED_FOR'` nor `'REMOTE_ADDR'` key is there. – yanchenko Jan 03 '11 at 05:40
  • How do I use the "get_client_ip" in my view? --Thank you! – avatar Jan 03 '11 at 06:07
  • If I use: lat,long = g.lat_lon(get_client_ip) I'm having a casting issue. It's expecting a string. – avatar Jan 03 '11 at 07:06
  • 9
    Call `ip = get_client_ip(request)` in your view function. – yanchenko Jan 03 '11 at 07:38
  • Still no luck. I get: GeoIP query must be a string, not type NoneType – avatar Jan 03 '11 at 13:49
  • I converted the ip into a string but when I pull the values for the "ip" and "lat" in the browser I see "None:. Here is the code: def home(request): g = GeoIP() ip = str(get_client_ip(request)) lat = g.lat_lon(ip) return render_to_response('home_page_tmp.html',locals()) – avatar Jan 03 '11 at 14:42
  • Figured it out. My fastcgi didn't pass the REMOTE_ADDR meta key. Thank you for your help. – avatar Jan 03 '11 at 15:27
  • 4
    The real client IP address is not the first but the last in HTTP_X_FORWARDED_FOR (see wikipedia page) – jujule Mar 04 '13 at 11:03
  • 6
    @jujule That's not correct. The format is typically [`X-Forwarded-For: client, proxy1, proxy2`](http://en.wikipedia.org/wiki/X-Forwarded-For). So the first address is the client's. – Michael Waterfall Feb 16 '14 at 09:36
  • 69
    This function is dangerous. With many setups a malicious user could easily cause this function to return any address they want (instead of their real one). See http://esd.io/blog/flask-apps-heroku-real-ip-spoofing.html – Eli Jun 06 '14 at 19:06
  • @yanchenko, Here i am gettng user internal ip address(192.xxx.x.xx), But how can i get the external ip(public ip) of a end user – Mulagala Nov 25 '14 at 07:03
  • I agree with Eli. None of existing answers here can perfectly solve multiple proxies and IP spoof problem. The answer in Eli's page is a better one – ZZY Nov 25 '14 at 10:25
  • 10
    From the django docs "relying on REMOTE_ADDR or similar values is widely known to be a worst practice" (https://www.djangoproject.com/weblog/2009/jul/28/security/#secondary-issue) – Zags May 07 '15 at 22:20
  • 2
    Don't use this. If your proxies (eg nginx) aren't configured properly, this method will start returning `127.0.0.1` for everything and you won't notice until 2 weeks later. Use the `django-ipware` library as suggested by [this answer](http://stackoverflow.com/questions/4581789/how-do-i-get-user-ip-address-in-django/16203978#16203978). – Boris Verkhovskiy May 09 '17 at 22:13
  • It always returns 127.0.0.1 no mater who opens the webpage – Ghasem Nov 12 '18 at 09:49
  • 1
    This is a great example of stack overflow's community wisdom failing - This answer may cause more problems than it solves potentially. There are circumstances where this works but there are also circumstances where it will break, which means it's not a good solution. When it does break, you're not going to know why and you may not be able to easily fix it. As in the answer below, use django-ipware and save yourself potential heartache. – ICW Aug 31 '19 at 19:52
  • 1
    I don't understand why people always want to check X-Forwarded-For conditionally. If you are sure you are behind a reverse proxy, you don't need the `REMOTE_ADDR` case. If you might be open to the public directly, everyone cna send an `X-Forwarded-For` header and pretend to be someone else. In both cases you won't want to use both of them. – SOFe Apr 08 '20 at 07:50
  • if your concern may be sql injection coming from IPADDR headers.. you can clean the values using built-in: from django.utils.html import strip_tags, escape. – Ezequiel Adrian Dec 26 '20 at 15:31
  • The answer in the link alleging the origin IP is always the last isn’t valid anymore. Check that question for more information. – zerohedge May 21 '21 at 13:15
  • Im on a LAN and always getting the same IP, from every computer – Eular Nov 10 '21 at 12:27
  • This does not work on the Kubernetes cluster. It returns the local IP address of the service resource. I will test django-ipware and add another comment – JM217 Jul 07 '22 at 21:35
263

You can use django-ipware which supports Python 2 & 3 and handles IPv4 & IPv6.

Install:

pip install django-ipware

Simple Usage:

# In a view or a middleware where the `request` object is available

from ipware import get_client_ip
ip, is_routable = get_client_ip(request)
if ip is None:
    # Unable to get the client's IP address
else:
    # We got the client's IP address
    if is_routable:
        # The client's IP address is publicly routable on the Internet
    else:
        # The client's IP address is private

# Order of precedence is (Public, Private, Loopback, None)

Advanced Usage:

  • Custom Header - Custom request header for ipware to look at:

    i, r = get_client_ip(request, request_header_order=['X_FORWARDED_FOR'])
    i, r = get_client_ip(request, request_header_order=['X_FORWARDED_FOR', 'REMOTE_ADDR'])
    
  • Proxy Count - Django server is behind a fixed number of proxies:

    i, r = get_client_ip(request, proxy_count=1)
    
  • Trusted Proxies - Django server is behind one or more known & trusted proxies:

    i, r = get_client_ip(request, proxy_trusted_ips=('177.2.2.2'))
    
    # For multiple proxies, simply add them to the list
    i, r = get_client_ip(request, proxy_trusted_ips=('177.2.2.2', '177.3.3.3'))
    
    # For proxies with fixed sub-domain and dynamic IP addresses, use partial pattern
    i, r = get_client_ip(request, proxy_trusted_ips=('177.2.', '177.3.'))
    

Note: read this notice.

theEpsilon
  • 1,800
  • 17
  • 30
Val Neekman
  • 17,692
  • 14
  • 63
  • 66
  • 23
    Take a look at its source code. It handles all the complications identified by the other answers here. – HostedMetrics.com Jun 09 '14 at 18:20
  • 5
    Thx @Heliodor -- Yep, I have made the module very simple for an average use-case and very flexible for a complex use-case. Minimally, you'd want to look at its github page before rolling your own. – Val Neekman Jul 26 '14 at 15:59
  • 3
    NOTE that django-ipware's settings are not secure by default! Anyone can pass one of the other variables and your site will log that IP. Always set `IPWARE_META_PRECEDENCE_LIST` to the variable that you use, or use an alternative like https://pypi.python.org/pypi/WsgiUnproxy – vdboor Nov 30 '15 at 11:28
  • @vdboor Could you elaborate a little? I can't find IPWARE_META_PRECEDENCE_LIST in the repo. – Monolith Apr 09 '16 at 07:12
  • @Monolith:Sorry, I copied their README typoo, the setting is called `IPWARE_META_PRECEDENCE_ORDER`. However, better use `WsgiUnproxy` as I mentioned before. – vdboor Apr 10 '16 at 15:55
  • It is actually called `IPWARE_META_PRECEDENCE_ORDER` and it is in ipware/defaults.py. You can set your own via the settings.py file. – Val Neekman Apr 10 '16 at 22:11
  • we now use `get_real_ip` instead of `get_client_ip` – ThaJay Oct 15 '19 at 14:08
  • 3
    @ThaJay Please note that as of 2.0.0, you should use `get_client_ip()`. `get_real_ip` is deprecated and will be removed in 3.0. – Val Neekman Oct 16 '19 at 17:26
  • @un33k Thank you for the information. We are still on `django-ipware==1.1.5` so `get_client_ip` does not exist yet. – ThaJay Oct 17 '19 at 16:09
  • This is so old.. is it still good or is there a better one ? – Ebram Shehata May 06 '22 at 01:26
94

Alexander's answer is great, but lacks the handling of proxies that sometimes return multiple IP's in the HTTP_X_FORWARDED_FOR header.

The real IP is usually at the end of the list, as explained here: http://en.wikipedia.org/wiki/X-Forwarded-For

The solution is a simple modification of Alexander's code:

def get_client_ip(request):
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        ip = x_forwarded_for.split(',')[-1].strip()
    else:
        ip = request.META.get('REMOTE_ADDR')
    return ip
Sævar
  • 1,602
  • 11
  • 18
  • 1
    Is it the IP at the *end* of the list? Reading that wikipedia link, it says "the left-most being the farthest downstream client", which sounds like the first value is the original client. Or does Django reverse it? – John C Nov 10 '11 at 14:17
  • 11
    Yup, the ip is at the **beginning** of the list. This here is wrong. – Pykler Nov 29 '11 at 15:44
  • 4
    Actually, if the user is behind a proxy you would get the user's internal IP address, i.e. a RFC 1918 address. In most cases, that's not very desirable at all. This solution focuses on getting the external IP address of the client (the proxy address), which is the right-most address. – Sævar Jan 21 '12 at 20:13
  • 2
    Thank you. Usually when I request keys from `request.META` I include a default value since headers are often mising: `request.META.get('REMOTE_ADDR', None)` – Carl G Jul 14 '12 at 05:28
  • 3
    @CarlG your code is more transparent, but the get method is inherited from django.utils.datastructures.MultiValueDict and the default value is None. But it definitely makes sense to include a default value if you actually wanted it to be something other than None. – Sævar Oct 09 '12 at 01:00
  • @Sævar it depends on whether the proxy is normal user proxy or a reverse proxy. Neither is reliable to identify the IP address of client's public/external IP. It heavily depends on the context. – Xiaofeng Tang Jul 25 '13 at 06:32
  • 2
    Unless you're scrubbing X-Forwarded-For when requests hit your first server, then the first value in that list is *user supplied*. A malicious user could easily spoof any IP address they want. The address you want is the first IP before any of your servers, not necessarily the first in the list. – Eli Jun 06 '14 at 19:09
29

No More confusion In the recent versions of Django it is mentioned clearly that the Ip address of the client is available at

request.META.get("REMOTE_ADDR")

for more info check the Django Docs

Pardhu
  • 1,789
  • 14
  • 17
  • 7
    This gives blank value when the application is running behind a reverse proxy server(like Nginx). You will need `X_FORWARDED_FOR` – Coderaemon Oct 12 '20 at 11:07
19

I would like to suggest an improvement to yanchenko's answer.

Instead of taking the first ip in the X_FORWARDED_FOR list, I take the first one which in not a known internal ip, as some routers don't respect the protocol, and you can see internal ips as the first value of the list.

PRIVATE_IPS_PREFIX = ('10.', '172.', '192.', )

def get_client_ip(request):
    """get the client ip from the request
    """
    remote_address = request.META.get('REMOTE_ADDR')
    # set the default value of the ip to be the REMOTE_ADDR if available
    # else None
    ip = remote_address
    # try to get the first non-proxy ip (not a private ip) from the
    # HTTP_X_FORWARDED_FOR
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        proxies = x_forwarded_for.split(',')
        # remove the private ips from the beginning
        while (len(proxies) > 0 and
                proxies[0].startswith(PRIVATE_IPS_PREFIX)):
            proxies.pop(0)
        # take the first ip which is not a private one (of a proxy)
        if len(proxies) > 0:
            ip = proxies[0]

    return ip

I hope this helps fellow Googlers who have the same problem.

Brenda J. Butler
  • 1,475
  • 11
  • 20
Doody P
  • 4,611
  • 2
  • 20
  • 18
  • This code does not check that the ip from REMOTE_ADDR is private before checking the HTTP_X_FORWARDED_FOR field, as it probably should (also, '127.0.0.1' or '127.' should probably be in PRIVATE_IPS_PREFIX, together with IPv6 equivalents. – Rasmus Kaj Mar 15 '15 at 12:54
  • 3
    Technically, those prefixes (172, 192) do not necessarily mean private addresses. – maniexx Jul 25 '15 at 08:17
  • 2
    The address ranges assigned for private networks are: 172.16.0.0–172.31.255.255 (16 “class B” networks), 192.168.0.0–192.168.255.255 (1 “class B” network) and 10.0.0.0–10.255.255.255 (1 “class A” or 256 “class B” networks). – tzot Oct 19 '17 at 09:27
  • is_valid_ip not defined – Prosenjit Jun 11 '19 at 10:31
  • I like this approach, but I think the implementation is a bit dangerous. For instance, most IP addresses under the 10. prefix are public IPs. T-Mobile owns 172.32.0.0 as an example. – monokrome Aug 27 '20 at 08:16
13

here is a short one liner to accomplish this:

request.META.get('HTTP_X_FORWARDED_FOR', request.META.get('REMOTE_ADDR', '')).split(',')[0].strip()
evilscientress
  • 171
  • 1
  • 5
  • 10
    If both of them return None then you would get an error. – Gourav Chawla Nov 27 '18 at 11:01
  • It really works, it returns your current public ip address, thanks @masterbase, you made my day. – Alvaro Castro Apr 27 '22 at 07:14
  • @GouravChawla some pretty simple conditionally logic would prevent this from erroring remote_ip = request.META.get('HTTP_X_FORWARDED_FOR', request.META.get('REMOTE_ADDR', '')) if remote_ip = remote_ip.split(',')[0] if remote_ip else None – kt-0 May 09 '22 at 19:44
7

In my case none of above works, so I have to check uwsgi + django source code and pass static param in nginx and see why/how, and below is what I have found.

Env info:
python version: 2.7.5
Django version: (1, 6, 6, 'final', 0)
nginx version: nginx/1.6.0
uwsgi: 2.0.7

Env setting info:
nginx as reverse proxy listening at port 80 uwsgi as upstream unix socket, will response to the request eventually

Django config info:

USE_X_FORWARDED_HOST = True # with or without this line does not matter

nginx config:

uwsgi_param      X-Real-IP              $remote_addr;
// uwsgi_param   X-Forwarded-For        $proxy_add_x_forwarded_for;
// uwsgi_param   HTTP_X_FORWARDED_FOR   $proxy_add_x_forwarded_for;

// hardcode for testing
uwsgi_param      X-Forwarded-For        "10.10.10.10";
uwsgi_param      HTTP_X_FORWARDED_FOR   "20.20.20.20";

getting all the params in django app:

X-Forwarded-For :       10.10.10.10
HTTP_X_FORWARDED_FOR :  20.20.20.20

Conclusion:

So basically, you have to specify exactly the same field/param name in nginx, and use request.META[field/param] in django app.

And now you can decide whether to add a middleware (interceptor) or just parse HTTP_X_FORWARDED_FOR in certain views.

Nrzonline
  • 1,600
  • 2
  • 18
  • 37
xxmajia
  • 638
  • 7
  • 6
7

The simpliest solution (in case you are using fastcgi+nignx) is what itgorilla commented:

Thank you for this great question. My fastcgi was not passing the REMOTE_ADDR meta key. I added the line below in the nginx.conf and fixed the problem: fastcgi_param REMOTE_ADDR $remote_addr; – itgorilla

Ps: I added this answer just to make his solution more visible.

Juande Carrion
  • 750
  • 8
  • 11
  • 3
    What's a comparable solution for nginx (reverse proxy) and gunicorn? `proxy_set_header REMOTE_ADDR $remote_addr;` doesn't alleviate the problem when added to nginx.conf. – Hassan Baig Dec 13 '15 at 10:58
4

The reason the functionality was removed from Django originally was that the header cannot ultimately be trusted. The reason is that it is easy to spoof. For example the recommended way to configure an Nginx reverse proxy is to:

add_header X-Forwarded-For $proxy_add_x_forwarded_for;
add_header X-Real-Ip       $remote_addr;

When you do:

curl -H 'X-Forwarded-For: 8.8.8.8, 192.168.1.2' http://192.168.1.3/

Your Nginx in myhost.example will send onwards:

X-Forwarded-For: 8.8.8.8, 192.168.1.2, 192.168.1.3

The X-Real-IP will be the IP of the first previous proxy if you follow the instructions blindly.

In case trusting who your users are is an issue, you could try something like django-xff: https://pypi.python.org/pypi/django-xff/

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
ferrix
  • 763
  • 3
  • 14
3

I was also missing proxy in above answer. I used get_ip_address_from_request from django_easy_timezones.

from easy_timezones.utils import get_ip_address_from_request, is_valid_ip, is_local_ip
ip = get_ip_address_from_request(request)
try:
    if is_valid_ip(ip):
        geoip_record = IpRange.objects.by_ip(ip)
except IpRange.DoesNotExist:
    return None

And here is method get_ip_address_from_request, IPv4 and IPv6 ready:

def get_ip_address_from_request(request):
    """ Makes the best attempt to get the client's real IP or return the loopback """
    PRIVATE_IPS_PREFIX = ('10.', '172.', '192.', '127.')
    ip_address = ''
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '')
    if x_forwarded_for and ',' not in x_forwarded_for:
        if not x_forwarded_for.startswith(PRIVATE_IPS_PREFIX) and is_valid_ip(x_forwarded_for):
            ip_address = x_forwarded_for.strip()
    else:
        ips = [ip.strip() for ip in x_forwarded_for.split(',')]
        for ip in ips:
            if ip.startswith(PRIVATE_IPS_PREFIX):
                continue
            elif not is_valid_ip(ip):
                continue
            else:
                ip_address = ip
                break
    if not ip_address:
        x_real_ip = request.META.get('HTTP_X_REAL_IP', '')
        if x_real_ip:
            if not x_real_ip.startswith(PRIVATE_IPS_PREFIX) and is_valid_ip(x_real_ip):
                ip_address = x_real_ip.strip()
    if not ip_address:
        remote_addr = request.META.get('REMOTE_ADDR', '')
        if remote_addr:
            if not remote_addr.startswith(PRIVATE_IPS_PREFIX) and is_valid_ip(remote_addr):
                ip_address = remote_addr.strip()
    if not ip_address:
        ip_address = '127.0.0.1'
    return ip_address
Lucas03
  • 2,267
  • 2
  • 32
  • 60
2

In django.VERSION (2, 1, 1, 'final', 0) request handler

sock=request._stream.stream.raw._sock
#<socket.socket fd=1236, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('192.168.1.111', 8000), raddr=('192.168.1.111', 64725)>
client_ip,port=sock.getpeername()

if you call above code twice,you may got

AttributeError("'_io.BytesIO' object has no attribute 'stream'",)

AttributeError("'LimitedStream' object has no attribute 'raw'")

CS QGB
  • 297
  • 1
  • 3
  • 12
1

Simply add

{{ request.META.REMOTE_ADDR }}

In Django-Template where you want the user to see their IP address. That is if you are not interested in saving this to the DB.

Surveyor Jr
  • 346
  • 2
  • 12
1

Get the ip address with this function:

def get_ip_address(request):
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        ip = x_forwarded_for.split(',')[0]
    else:
        ip = request.META.get('REMOTE_ADDR')
    return ip

after that you can get the user location data and other info from that web app http://www.iplocinfo.com/:

import requests
def get_ip_data(request):
    ip_address = get_ip_address(request)
    api_key = "your api key"
    endPoint = f'https://www.iplocinfo.com/api/v1/{ip_address}?apiKey={api_key}'
    data = requests.get(endPoint)
    return data.json()
Solve
  • 11
  • 2
0

After getting ip address you need to find location

# pip install geocoder

import geocoder

def get_client_ip(request):
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        ip = x_forwarded_for.split(',')[0]
        ip_location = geocoder.ip(f"{ip}")
        ip_location = geocoder.ip("me")
        print(ip_location.city)
        # you can get city such as "New York"
    else:
        ip = request.META.get('REMOTE_ADDR')
    return ip