Get the IP Address in JavaScript: Browser, Node.js, Express and Next.js
Short answer
Browser JavaScript cannot read the user’s IP address, so call an endpoint on your own server that returns the address of the request. In Node.js that address is req.socket.remoteAddress, or req.ip in Express. Behind a proxy or CDN, set Express trust proxy to your proxies’ addresses so req.ip shows the visitor instead of the proxy.
On this page
- Where your code runs decides how you get the IP
- Get the user’s IP address in browser JavaScript
- Get the client IP address on the server
- Get the client IP behind a proxy, load balancer or CDN
- Validate the IP address before you use it
- Get the local IP address in JavaScript
- Get the public IP address of your server or machine
- Get the location from an IP address in JavaScript
- Privacy: IP addresses are personal data
- Troubleshooting: the IP address is wrong or missing
An IP address belongs to the network connection, not to the page, so browser JavaScript cannot read it. Your server can: it sees the address of every request and can send it back. This guide covers the browser, Node.js and its frameworks, proxies, validation, the machine’s own local and public IP, and turning an IP into a location.
Where your code runs decides how you get the IP
| Where your code runs | What to use | Notes |
|---|---|---|
| Browser (plain JavaScript, React, Vue) | fetch() an endpoint on your server | No web API returns the IP |
Node.js node:http | req.socket.remoteAddress | The proxy’s address behind a proxy |
| Express 4 and 5 | req.ip, chain in req.ips | Set trust proxy behind a proxy |
| Fastify 5 | request.ip | trustProxy takes addresses, not hop counts |
| Next.js on Vercel | ipAddress(request) from @vercel/functions | request.ip was removed in Next.js 15 |
| Next.js, self-hosted | The header your reverse proxy sets, such as x-real-ip | Only your proxy may reach Next.js |
| Cloudflare Workers | request.headers.get("CF-Connecting-IP") | Set by Cloudflare’s edge |
| Netlify Functions | context.ip | Second handler argument |
| AWS Lambda (function URL, HTTP API) | event.requestContext.http.sourceIp | REST APIs: event.requestContext.identity.sourceIp |
| Deno | info.remoteAddr.hostname in Deno.serve() | Second handler argument |
| Bun | server.requestIP(req)?.address | null for Unix sockets |
| Node.js script, for the machine’s own IP | os.networkInterfaces(); for the public IP, an API call | See below |
Get the user’s IP address in browser JavaScript
No web API returns the public IP address, and the device often does not have it: at home it uses a private address, and the router translates that to the public one. Only the server at the other end of the connection sees the public address. This is the one a server sees for you right now:
Your public IP address
216.73.216.128
- Location
- Columbus, Ohio, United States
- Provider
- Anthropic, PBC (AS16509)
- Time zone
- America/New_York (UTC−4)
- Coordinates
- 39.96, -83.00
This is the address websites see, and the location is an estimate that can be off by a city or more. Your device's local IP (often 192.168.x.x) is a different one and only works inside your network.
If the box shows an IPv6 address, your connection prefers IPv6; a server reachable only over IPv4 sees your IPv4 address instead.
To use the address in a page, return it from an endpoint on your own server and fetch it:
// In the browser (a module script or the DevTools console)
const res = await fetch("/api/ip", { cache: "no-store" });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const { ip } = await res.json();
console.log(ip); // for example 203.0.113.7 or 2001:db8::7
Serve the endpoint from the page’s origin, or allow that origin with CORS, and send Cache-Control: no-store: a CDN or browser cache that stores /api/ip would hand one visitor’s IP to the next. In React or Vue, call it from an effect or a data loader.
The endpoint takes a few lines on any platform, for example a Cloudflare Worker:
// src/index.js: Cloudflare Worker
export default {
fetch(request) {
const ip = request.headers.get("CF-Connecting-IP"); // set by Cloudflare's edge
return Response.json({ ip }, { headers: { "Cache-Control": "no-store" } });
},
};Why not a third-party script or WebRTC?
Warning
Snippets that call an IP service straight from the browser depend on a service you do not control or expose an API key. Never put a key into browser JavaScript, including NEXT_PUBLIC_ and VITE_ environment variables: they end up in the bundle every visitor downloads, and anyone can copy the key and use up your quota.
WebRTC does not help either. A peer connection learns a public address only from a STUN or TURN server you configure, as a server-reflexive candidate (srflx in MDN’s RTCIceCandidate reference). That needs a third-party server and UDP traffic that networks and extensions can block, and the result can differ from the address your web server sees. Local addresses are hidden altogether, as explained below.
Get the client IP address on the server
Without a proxy in front, these methods return the visitor’s IP. Behind a proxy, CDN or load balancer, they return the proxy’s IP until you configure trust (next section).
Node.js http module: req.socket.remoteAddress
// server.mjs: Node.js without a framework
import { createServer } from "node:http";
createServer((req, res) => {
const ip = req.socket.remoteAddress; // the other end of the TCP connection
res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" });
res.end(JSON.stringify({ ip }));
}).listen(3000);req.connection is a deprecated alias, so use req.socket. The value becomes undefined once the client has disconnected, so read it at the start of the handler. When listen() gets no host on a machine with IPv6, Node.js binds to ::, which on most systems accepts IPv4 too, and IPv4 clients then appear as ::ffff:203.0.113.7.
Express: req.ip and req.ips
// server.mjs: Express 5 (Express 4 works the same)
import express from "express";
const app = express();
app.get("/api/ip", (req, res) => {
res.set("Cache-Control", "no-store"); // never serve one visitor's IP to another
res.json({ ip: req.ip });
});
app.listen(3000);Without a proxy setting, req.ip is the socket address. With trust proxy set, it comes from X-Forwarded-For, and req.ips lists the client followed by the proxies that forwarded the request.
Fastify: request.ip
// server.mjs: Fastify 5
import Fastify from "fastify";
// nginx on the same host plus a load balancer subnet: list them, never a hop count
const app = Fastify({ trustProxy: ["127.0.0.1", "::1", "10.0.0.0/8"] });
app.get("/api/ip", async (request, reply) => {
reply.header("Cache-Control", "no-store");
return { ip: request.ip };
});
await app.listen({ port: 3000 });trustProxy takes your proxies’ addresses or subnets as an array or a comma-separated string. Fastify’s documentation marks hop counts as unsupported, and current 5.x releases then trust no proxy, so an old trustProxy: 1 leaves request.ip at the proxy’s address.
Next.js: route handlers and Server Components
// app/api/ip/route.js: Next.js 15 or 16 on Vercel
import { ipAddress } from "@vercel/functions";
export function GET(request) {
const ip = ipAddress(request) ?? null; // undefined outside Vercel, for example in next dev
return Response.json({ ip }, { headers: { "Cache-Control": "no-store" } });
}Next.js 15 removed request.ip, because the hosting platform provides the address (Next.js 15 upgrade guide). On Vercel, ipAddress() reads the x-real-ip header, which Vercel fills with the client’s address. In Server Components, read it with (await headers()).get("x-real-ip") from next/headers.
Self-hosted with next start, Next.js fills x-forwarded-for with the connection’s address only when the request has none, and it passes client-sent x-forwarded-for and x-real-ip headers through unchanged. Put nginx in front, bind Next.js to 127.0.0.1 and read the header nginx sets.
Cloudflare Workers, Netlify, AWS Lambda, Deno and Bun
Serverless platforms pass the address instead of a socket. For the Worker above: in local wrangler dev, CF-Connecting-IP is 127.0.0.1 and a value you send yourself passes through, so test spoofing on a deployed Worker.
// netlify/functions/ip.mjs
export default async (req, context) =>
Response.json({ ip: context.ip }, { headers: { "Cache-Control": "no-store" } });
export const config = { path: "/api/ip" };// index.mjs: AWS Lambda behind a function URL or an API Gateway HTTP API
export const handler = async (event) => ({
statusCode: 200,
headers: { "Content-Type": "application/json", "Cache-Control": "no-store" },
body: JSON.stringify({ ip: event.requestContext?.http?.sourceIp ?? null }),
});AWS documents sourceIp as the source of the immediate TCP connection, so behind CloudFront it holds CloudFront’s address.
// server.js for Deno: deno run --allow-net server.js
Deno.serve((req, info) =>
Response.json({ ip: info.remoteAddr.hostname }, { headers: { "Cache-Control": "no-store" } }),
);// server.js for Bun: bun server.js
Bun.serve({
port: 3000,
fetch(req, server) {
return Response.json({ ip: server.requestIP(req)?.address ?? null });
},
});Get the client IP behind a proxy, load balancer or CDN
Behind nginx, a load balancer or a CDN, every method above returns the proxy’s address. The proxy passes the visitor’s address in a header, usually X-Forwarded-For: <client>, <proxy1>, <proxy2>, with each hop appending the address it received the request from. The client can write anything into it before the first proxy, so only the entries your own proxies added are trustworthy.
In Express, give trust proxy the addresses of your proxies, as the Express guide to running behind proxies describes:
// nginx on the same host, plus a load balancer in 10.0.0.0/8
app.set("trust proxy", ["loopback", "10.0.0.0/8"]);Express reads X-Forwarded-For from the right, skips your proxies and returns the first address that is not one of them. Say a visitor at 203.0.113.7 forges X-Forwarded-For: 198.51.100.66 and passes a load balancer at 10.0.1.23 and nginx on 127.0.0.1:
trust proxy | req.ip | Result |
|---|---|---|
false (default) | ::ffff:127.0.0.1 | nginx, as an IPv4-mapped address |
["loopback", "10.0.0.0/8"] | 203.0.113.7 | The visitor |
1 | 10.0.1.23 | The load balancer |
true | 198.51.100.66 | The forged entry |
A hop count works only when every request takes the same path. true returns whatever the client wrote, and so does the common req.headers["x-forwarded-for"].split(",")[0]. Settings for each CDN, cloud and web server are in how to get the real client IP behind proxies and CDNs, and the X-Forwarded-For header guide explains the header’s format.
Validate the IP address before you use it
Treat req.ip and every header value as untrusted input. Express copies X-Forwarded-For entries as they are, so a port such as 203.0.113.7:4711 or text such as unknown can arrive in req.ip. Normalize the value once and pass only the result to logs, rate limits and lookups:
// lib/client-ip.js
import { isIP } from "node:net";
// A plain IPv4 or IPv6 address, or null for anything else
export function normalizeIp(value) {
if (typeof value !== "string") return null;
let ip = value.trim();
if (/^::ffff:/i.test(ip) && isIP(ip.slice(7)) === 4) ip = ip.slice(7); // IPv4-mapped IPv6
return isIP(ip) ? ip : null;
}net.isIP() returns 4 for IPv4, 6 for IPv6 and 0 for anything else, including addresses with a port, CIDR ranges and IPv4 with leading zeros. The helper also turns ::ffff:203.0.113.7 into 203.0.113.7, so one visitor does not appear under two spellings.
Get the local IP address in JavaScript
The local IP is the private address of a device in its own network, such as 192.168.1.108. Websites never see it.
In the browser: not possible
WebRTC used to reveal local addresses in ICE host candidates. Since Chrome 76 (2019), Chrome replaces the address with a random name ending in .local (an mDNS hostname), except on pages that have camera or microphone permission. Firefox and Safari also hide local addresses. To see what your browser exposes, paste this into the DevTools console:
// Lists the ICE candidates this page can see
const pc = new RTCPeerConnection(); // no STUN server: local (host) candidates only
pc.onicecandidate = ({ candidate }) => candidate && console.log(candidate.candidate);
pc.createDataChannel("probe");
await pc.setLocalDescription(await pc.createOffer());Chrome prints a host candidate with a .local name where the IP address used to be. If an app needs the local address, let the device or a local agent report it, or ask the user.
In Node.js: os.networkInterfaces()
// local-ip.mjs: the LAN addresses of this machine
import { networkInterfaces } from "node:os";
export function localAddresses(interfaces = networkInterfaces()) {
const result = [];
for (const [name, addresses] of Object.entries(interfaces)) {
for (const { address, family, internal } of addresses ?? []) {
const v4 = family === "IPv4" || family === 4; // Node.js 18.0-18.3 returned numbers
if (internal) continue; // loopback: 127.0.0.1, ::1
if (v4 && address.startsWith("169.254.")) continue; // self-assigned, no DHCP lease
if (!v4 && /^fe[89ab]/i.test(address)) continue; // IPv6 link-local fe80::/10
result.push({ name, family: v4 ? "IPv4" : "IPv6", address });
}
}
return result;
}
console.log(localAddresses());On a Mac on Wi-Fi, the output looks like this (example values):
[
{ name: 'en0', family: 'IPv4', address: '192.168.1.108' },
{ name: 'en0', family: 'IPv6', address: '2001:db8:4f2a:1::3c1a' }
]Computers often have several addresses: Wi-Fi and Ethernet, IPv4 and IPv6, plus temporary IPv6 addresses. Docker, VPN clients and virtual machines add adapters that look like LAN interfaces, and inside a container you get the container’s address, not the host’s.
The local address your machine uses for the internet
To get the one address the operating system uses for outgoing traffic, let it choose a route:
// outbound-ip.mjs: the local address of the interface with the default route
import { createSocket } from "node:dgram";
export function outboundAddress(family = 4) {
const target = family === 6 ? "2001:db8::1" : "198.51.100.1"; // documentation addresses
return new Promise((resolve, reject) => {
const socket = createSocket(family === 6 ? "udp6" : "udp4");
socket.on("error", reject);
// connect() on a UDP socket only picks a route and a source address: nothing is sent
socket.connect(53, target, (err) => {
if (err) {
socket.close();
return reject(err);
}
const { address } = socket.address();
socket.close();
resolve(address);
});
});
}
console.log(await outboundAddress()); // for example 192.168.1.108
Any address outside your network works as the target. Without IPv6, outboundAddress(6) rejects with an error.
Get the public IP address of your server or machine
Behind a home router, a cloud NAT gateway or a company network, the public address belongs to the gateway, not the machine. To find it, call the iplookupapi info endpoint from the machine without an ip parameter; it answers for the address the request came from:
// public-ip.mjs: the public IP this machine uses on the internet
const res = await fetch("https://api.iplookupapi.com/v1/info?fields=ip,type", {
headers: { apikey: process.env.IPLOOKUPAPI_KEY },
signal: AbortSignal.timeout(5000),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const { data } = await res.json();
console.log(data.ip, data.type); // for example: 203.0.113.7 v4
IPLOOKUPAPI_KEY=your_api_key node public-ip.mjstype shows whether the request went out over IPv4 (v4) or IPv6 (v6). Every call counts toward your monthly quota, so look the address up once and cache it.
Get the location from an IP address in JavaScript
An IP geolocation API turns a validated client IP into a country, city, time zone and network. Make the call on the server, with the key from the environment, a short timeout and a fallback for failed lookups:
// lib/ip-location.js: server-side only
const FIELDS = "ip,location.country.alpha2,location.city.name,timezone.id,security.is_vpn";
export async function ipLocation(ip) {
const url = new URL("https://api.iplookupapi.com/v1/info");
url.searchParams.set("ip", ip);
url.searchParams.set("fields", FIELDS);
const res = await fetch(url, {
headers: { apikey: process.env.IPLOOKUPAPI_KEY },
signal: AbortSignal.timeout(3000), // fail fast rather than hang the request
});
if (!res.ok) throw new Error(`IP lookup failed with HTTP ${res.status}`);
const { data } = await res.json();
return {
country: data.location?.country?.alpha2 ?? null, // "AT"
city: data.location?.city?.name ?? null, // "Vienna"
timeZone: data.timezone?.id ?? null, // "Europe/Vienna"
vpn: data.security?.is_vpn ?? null, // null on plans without security data
};
}// server.mjs: Express route with normalizeIp() from above
import express from "express";
import { normalizeIp } from "./lib/client-ip.js";
import { ipLocation } from "./lib/ip-location.js";
const app = express();
app.set("trust proxy", "loopback"); // nginx on this host
app.get("/api/location", async (req, res) => {
res.set("Cache-Control", "no-store");
const ip = normalizeIp(req.ip);
if (!ip) return res.status(400).json({ error: "No valid client IP" });
try {
res.json({ ip, ...(await ipLocation(ip)) });
} catch (err) {
console.warn(err.name === "TimeoutError" ? "IP lookup timed out" : err.message);
res.json({ ip, country: null, city: null, timeZone: null, vpn: null }); // the page works without it
}
});
app.listen(3000);For the documentation’s sample address, 80.110.0.1, the API answers with these fields:
{
"data": {
"ip": "80.110.0.1",
"location": {
"country": { "alpha2": "AT" },
"city": { "name": "Vienna" }
},
"timezone": { "id": "Europe/Vienna" },
"security": { "is_vpn": false }
}
}Location, connection and time zone data come with every plan, including the Free plan with 150 requests a month. Security flags such as is_vpn start at the Professional plan and are null below it. The JavaScript SDK guide adds batch lookups, retries and caching, the info endpoint reference lists every field, and the IP to country API covers country codes, currencies and EU membership.
IP location is an estimate: the country is usually right, the city often is not. For a device’s exact position, use the browser’s Geolocation API (navigator.geolocation), which asks the user for permission.
Privacy: IP addresses are personal data
Under the GDPR, IP addresses are online identifiers (Recital 30) and usually count as personal data. Recital 49 treats processing that is strictly necessary for network and information security as a legitimate interest, which covers logs for rate limits and abuse detection. Still, collect only what each feature needs:
- Keep raw IP addresses in logs for a short, fixed period.
- Store the derived country instead of the address when that is all a feature uses.
- Shorten addresses for statistics, for example to the /24 network for IPv4 or the /48 for IPv6.
- Do not treat an IP address as a user ID: carrier-grade NAT puts many users behind one address, and one user’s address changes often.
Troubleshooting: the IP address is wrong or missing
| Symptom | Cause | Fix |
|---|---|---|
Always 127.0.0.1 or ::1 | Local testing, or a proxy on the same host | Test from another device; trust loopback behind a local proxy |
The same 10.x, 172.16.x or CDN address for everyone | A load balancer or CDN connects to your app | Trust its addresses |
::ffff:203.0.113.7 | An IPv4 client on a dual-stack socket | Strip the prefix with normalizeIp() |
undefined | The client disconnected, or there is no socket (Next.js route handlers, Workers), or request.ip in Next.js 15+ | Read the address first; use the platform’s header or context |
| An address the visitor does not have | Your code trusts a header the client sent | Trust only your proxies |
| IPv6 where you expected IPv4 | The visitor connected over IPv6 | Accept both; text columns need 45 characters |
| No location in development | Loopback and private addresses have no location | Test on a deployed app |
The same tasks in Python, with Flask, Django and FastAPI, are in get the IP address in Python. More guides are on the developer hub.
Frequently asked questions
How do I get the user's IP address in JavaScript?
Read it on your server and send it to the page, because browser JavaScript has no API for it. Add an endpoint such as /api/ip that returns req.ip in Express, the CF-Connecting-IP header in a Cloudflare Worker or context.ip in a Netlify Function, and call it with fetch(). Behind a proxy or CDN, first tell your framework which proxies to trust, as the real client IP guide shows.
Can JavaScript get the client's IP address without a server?
No. The public IP address belongs to the network connection, and only the server at the other end sees it. WebRTC can report a public address when you configure a third-party STUN server, but networks, extensions and privacy settings can block it, and it may differ from the address your web server sees. Every reliable method asks a server, ideally your own.
How do I get the local IP address in JavaScript?
In the browser you cannot: since Chrome 76 (2019), Chrome replaces local addresses in WebRTC candidates with random .local names, and Firefox and Safari also hide them. In Node.js, os.networkInterfaces() lists every address of the machine; skip internal and link-local entries to get the LAN address, such as 192.168.1.108. The local IP address guide explains the private ranges.
How do I get the client IP in Node.js?
Read req.socket.remoteAddress with the node:http module, req.ip in Express and request.ip in Fastify. Behind nginx, a load balancer or a CDN these return the proxy’s address until you tell the framework which proxies to trust, for example app.set(’trust proxy’, [’loopback’, ‘10.0.0.0/8’]) in Express. Never use the first entry of X-Forwarded-For, because the client can set it.
Why does req.ip return ::1 or 127.0.0.1?
The request arrived over the loopback interface. Either you are testing on your own machine, or a reverse proxy such as nginx runs on the same host and Express does not trust it yet; then set app.set(’trust proxy’, ’loopback’). A value such as ::ffff:127.0.0.1 is the same IPv4 address written as an IPv4-mapped IPv6 address.
How do I get the location from an IP address in JavaScript?
Send the address from your server to an IP geolocation API and read the JSON response. For example, GET https://api.iplookupapi.com/v1/info?ip=8.8.8.8 with your key in the apikey header returns the country, city, time zone and network, plus VPN and proxy flags from the Professional plan. Keep the call on the server so the key stays private; the JavaScript SDK guide adds batching, retries and caching.
Look up IP addresses in your code
iplookupapi returns the location, provider, time zone and security flags of any IP address as JSON. The free plan includes 150 requests a month.
curl "https://api.iplookupapi.com/v1/info?ip=8.8.8.8" \
-H "apikey: YOUR_API_KEY"