os.networkInterfaces()
Returns an object of the machine's network interfaces and the addresses assigned to each.
os.networkInterfaces()- Added in Node.js v0.6.0
Return value
ObjectAn object whose keys are interface names and whose values are arrays of assigned-address objects, each with `address`, `netmask`, `family` ('IPv4' or 'IPv6'), `mac`, `internal`, `cidr`, and `scopeid` for IPv6. Interfaces with no assigned address are omitted.
Changelog
- PHP v18.4.0The family property returns a string ('IPv4'/'IPv6') again instead of a number.
- PHP v18.0.0The family property returned a number (4/6) instead of a string.
os.networkInterfaces() reads the local interface configuration from the kernel. It sends no packets and makes no DNS queries — it is a snapshot of what addresses this machine has, not a probe of what it can reach.
When to reach for it
Printing a "listening on http://192.168.1.14:3000" line for a dev server, deciding which address to advertise when registering with a service discovery system, or checking whether a machine has any non-loopback connectivity at all.
What trips people up
There is no single "the IP address". A laptop typically has loopback, Wi-Fi, Ethernet, a VPN tunnel and Docker bridges, each with IPv4 and IPv6 entries — you must filter, and the order is not meaningful. Filter on internal === false to drop loopback, and on family === 'IPv4' if you want a v4 address.
Watch that family field on Node 18.0–18.3, where it was briefly a number (4/6) before being reverted to a string in 18.4. Code written against that window silently matches nothing today.
Interfaces with no assigned address are omitted entirely, so a down interface simply will not appear. Results change as networks come and go, so re-read rather than caching at startup on a laptop.
Example
When to use it
- Print the LAN address a dev server is reachable at
- Pick the address to advertise when registering with service discovery
- Check whether any non-loopback interface exists before starting a network feature
Frequently asked questions
How do I get the local IP address of a machine in Node.js?
Flatten os.networkInterfaces() and take the first entry where family === 'IPv4' and internal === false. There may be several — Wi-Fi, Ethernet, VPN and Docker bridges all qualify — so if the choice matters, pick by interface name or by which subnet you expect.
Why is os.networkInterfaces().family sometimes a number?
Node 18.0.0 changed family from the string 'IPv4'/'IPv6' to the number 4/6, and 18.4.0 reverted it. Only releases in that narrow window return numbers; write the check as a.family === 'IPv4' || a.family === 4 if you must support them.
Does os.networkInterfaces() tell me my public IP address?
No. It only lists addresses assigned to local interfaces, so behind NAT you see a private address such as 192.168.x.x and never the public one. Discovering a public address requires an external service, which means an actual network request.
More examples
Find the LAN address to print for a dev server
This is how dev servers produce their two-line startup banner. The null case is real and must be handled: an isolated container or a machine with networking down has only loopback, and blindly indexing the filtered array would throw on undefined.
const os = require('node:os');
function lanAddress() {
for (const [name, addrs] of Object.entries(os.networkInterfaces())) {
for (const a of addrs ?? []) {
// family is the string 'IPv4' on Node 16 and on 18.4+.
if (a.family === 'IPv4' && !a.internal) return { name, address: a.address };
}
}
return null;
}
const lan = lanAddress();
console.log('lan interface:', lan ?? 'none (loopback only)');
console.log('local url:', 'http://localhost:3000');
console.log('network url:', lan ? `http://${lan.address}:3000` : 'unavailable');Group every address by interface
The per-interface counts show why there is no single machine IP: a typical laptop lists loopback plus Wi-Fi, a VPN tunnel and Docker bridges, most carrying both families. Loopback reports a MAC of 00:00:00:00:00:00, which is the giveaway that MAC is not a reliable machine identifier.
const os = require('node:os');
const nets = os.networkInterfaces();
for (const [name, addrs] of Object.entries(nets)) {
const v4 = addrs.filter((a) => a.family === 'IPv4').length;
const v6 = addrs.filter((a) => a.family === 'IPv6').length;
const mac = addrs[0]?.mac ?? '??';
const internal = addrs.every((a) => a.internal);
console.log(`${name}: ${v4} IPv4, ${v6} IPv6, mac ${mac}, internal ${internal}`);
}
console.log('total interfaces:', Object.keys(nets).length);
console.log('total addresses:', Object.values(nets).flat().length);Handle the family type across Node versions
On Node 22 the family type prints as "string" and the numeric half of each predicate never fires. Keeping both comparisons costs nothing and is the difference between a library working and silently reporting zero addresses on an early Node 18 release.
const os = require('node:os');
// Node 18.0-18.3 returned the number 4/6 here; everything else returns a string.
const isIPv4 = (a) => a.family === 'IPv4' || a.family === 4;
const isIPv6 = (a) => a.family === 'IPv6' || a.family === 6;
const all = Object.values(os.networkInterfaces()).flat();
console.log('node version:', process.versions.node);
console.log('family type here:', typeof all[0]?.family);
console.log('IPv4 addresses:', all.filter(isIPv4).length);
console.log('IPv6 addresses:', all.filter(isIPv6).length);
console.log('loopback only:', all.every((a) => a.internal));
Discussion