How to Get User’s IP Details in Express

An Express app can use a visitor’s IP address for jobs like rate limits or an approximate location lookup. Express gives you that address as req.ip, though behind a proxy it needs one setting to report the visitor instead of the proxy.

I’ll show you how to get the right address with req.ip and trust proxy, then look up its approximate location with geoip-lite.

What is req.ip in Express?

Express sets req.ip to the address it identifies for the request. With the default proxy setting, that address comes from the network connection to your app, which can be your reverse proxy rather than the visitor.

When Express trusts a proxy, it evaluates the forwarded address chain and selects the first address outside the trusted proxy chain. The reverse-proxy setup therefore has to match the proxy trust rule in your app.

Request pathWhat req.ip representsSetting
Client connects directly to ExpressRemote address of the connectionDefault trust proxy setting
Client connects through a trusted proxyClient address selected from the forwarded chainTrust only the proxy hops your deployment controls
Forwarded header arrives from an untrusted peerConnection peer, not the header’s claimed clientLeave that peer untrusted

Prerequisites

The tested endpoint ran on Node.js 26.7.0 and Express 5.2.1, with geoip-lite 2.0.3 for optional location lookups.

  • For a direct connection, no proxy configuration is required.
  • For a proxied connection, identify which proxy address or network connects to Express and how that proxy handles X-Forwarded-For.
  • Install geoip-lite only if you need an approximate country or region lookup.

Step 1: Return the address from an Express route

With Express’s default setting, req.ip comes from the connection peer, which can be your reverse proxy rather than a visitor.

const express = require('express');
const app = express();

app.get('/ip', (req, res) => {
  res.json({ ip: req.ip });
});

app.listen(3000, () => console.log('Listening on port 3000'));

A local request commonly reports loopback because the client and server share one machine, not a public visitor’s address.

Step 2: Trust only the proxy in front of Express

Set proxy trust only after identifying which proxy connects to Express and checking how it handles forwarded headers.

app.set('trust proxy', 'loopback');
DeploymentTrust setting decision
Known local proxy at loopbackUse the loopback subnet only when that proxy sanitizes forwarded headers
Proxy on a known address or subnetTrust the documented proxy address or subnet
Several proxy hopsMatch every ingress path and trust only the controlled hops
Unknown or changing hop countDo not trust a hop count that could route around the trusted proxy

Express sets req.ip from the forwarded chain and exposes its evaluated addresses through req.ips. A shared hop count fails when ingress paths differ.

Do not replace this setting with a direct read of req.headers[‘x-forwarded-for’]. A caller can send that header unless the trusted proxy overwrites or safely handles it, so an untrusted value is not a source for rate limits or access decisions.

Step 3: Look up approximate location

Install the current geoip-lite package in your project before adding the lookup. The package reads a local GeoIP database and returns approximate network-location data, not a person’s identity or precise position.

npm install geoip-lite

GeoIP databases map a public address to approximate location, and geoip-lite can return null for private addresses or addresses missing from its database.

npm install geoip-lite

The test lookup for 8.8.8.8 returns a country and timezone from the local GeoIP database. That result describes the test address, not a visitor.

const geoip = require('geoip-lite');

app.get('/ip', (req, res) => {
  const ip = req.ip;
  const location = geoip.lookup(ip);
  res.json({ ip, location });
});

The lookup can return null when the address is private, reserved, or absent from the installed database. In the test output below, the database maps the test address 8.8.8.8 to the United States, but that is a lookup result for Google’s public DNS address, not a visitor’s location.

Step 4: Verify the trusted and untrusted cases

The smoke test uses a local connection as a controlled proxy simulation and sends X-Forwarded-For: 8.8.8.8. Express returns that address in the app configured to trust loopback, while the default app reports the socket address, 127.0.0.1.

node smoke-test.js
Terminal output showing Express trusting a loopback proxy and ignoring an untrusted forwarded address
The test harness sends a forwarded address through a local socket. Express uses it only in the app configured to trust loopback.

This is a local simulation of the two Express settings, not a test of a cloud load balancer or your production proxy. Replace the loopback setting with the addresses and header behavior from your own deployment before relying on its output.

Fix proxy and GeoIP mismatches

If the returned address looks wrong, compare the socket peer, the proxy chain, and the trust setting before changing the route code.

SymptomLikely causeWhat to change
req.ip is the proxy’s private addressExpress does not trust that proxyConfigure trust proxy for the proxy’s actual address or subnet
req.ip matches a forged headerThe app trusts a proxy that passes client-supplied X-Forwarded-For unchangedMake the proxy sanitize the header or stop trusting that hop
req.ip changes across routesIngress paths use different proxy counts or peersUse explicit trusted proxy addresses instead of a shared hop count
geoip.lookup returns nullThe address is private, reserved, or missing in the local databaseTest with a public address and handle null in the response

The X-Forwarded-For chain runs from the client toward proxies that appended addresses. Express walks from the connection peer through trusted hops, so check your proxy’s exact behavior against the Express proxy guide and the MDN header security notes before trusting the selected address.

Use the address only for decisions it supports

For request logging or approximate regional content, req.ip may provide the signal you need once proxy trust matches the deployment. It is not proof of a person’s identity, and neither the forwarded header nor a GeoIP lookup should stand in for authentication.

console.log({ ip: req.ip, proxyChain: req.ips });

Express IP address questions

These answers cover the request property, proxy trust and the limit of a GeoIP lookup.

How do I get a user’s IP address in Express?

Read req.ip inside the route. If Express sits behind a reverse proxy, configure trust proxy to match the proxy addresses and how they handle forwarded headers.

Why does Express return the proxy’s IP address?

Express uses the connection peer by default. It uses forwarded addresses only when trust proxy settings identify the relevant proxy as trusted.

Can GeoIP lookup identify a person?

No. A GeoIP database estimates an address’s location, and its lookup can return null. An address or location estimate does not prove a person’s identity.

Pankaj Kumar
Pankaj Kumar

Pankaj Kumar is the founder and CEO of CodeForGeek, with more than 14 years in IT. He is an open-source enthusiast who enjoys sharing what he learns through CodeForGeek and YouTube, with a focus on Python, data analytics, machine learning, Angular, Node.js, and Kafka.

Articles: 337