Introduction: Why "just REMOTE_ADDR" no longer works
Every PHP developer has at least once asked the question: "How to get a client's IP address?". On Stack Overflow, this topic gathers hundreds of thousands of views, and under the canonical answer, there are dozens of clarifying comments. The problem is that in 2025, a simple $_SERVER['REMOTE_ADDR'] is no longer sufficient.
Modern websites live behind CDNs (Cloudflare, Akamai), load balancers, and proxy servers. Users connect via VPN or Tor. And attackers have learned to spoof headers. If you want to find out the real IP address of a visitor — and not open the door to attacks — you need to apply proven practices.
In this article, I will show how IP determination works in PHP, break down the pitfalls, and provide ready-to-use code that you can safely use in 2025.
How does PHP "see" the IP address?
When a browser sends a request to the server, PHP automatically populates the superglobal array $_SERVER. The key REMOTE_ADDR is responsible for the "interlocutor's" IP address. In an ideal world (without proxies), this is the client's IP:
<?php$ip = $_SERVER['REMOTE_ADDR']; // 192.168.1.5?>But in reality, behind the server there is often nginx, HAProxy, or Cloudflare. Then REMOTE_ADDR shows the proxy's IP, not the user's. The real address is passed in HTTP headers:
HTTP_X_FORWARDED_FOR— standard header for proxies;HTTP_X_REAL_IP— used by nginx;HTTP_CF_CONNECTING_IP— specific to Cloudflare;HTTP_CLIENT_IP— rarely, but it occurs.
The problem: any of these headers can be spoofed. If you trust $_SERVER['HTTP_X_FORWARDED_FOR'] without verification, an attacker can easily substitute a different IP.
REMOTE_ADDR vs X-Forwarded-For: When to use what?
Here is the key rule that will save you from headaches:
- If the site is hosted directly on the server without a proxy — use only
REMOTE_ADDR. - If the site is behind a trusted proxy (CDN, load balancer) — take the IP from the header, but only after verifying that the request came specifically from your proxy.
In 2025, the de facto standard is X-Forwarded-For. Inside it, there can be a chain of IPs: client, proxy1, proxy2. The first address is the client's, the last is the address of the last proxy.
Why can't you blindly trust X-Forwarded-For?
Imagine: an attacker sends a request with the header X-Forwarded-For: 127.0.0.1. If your code simply reads $_SERVER['HTTP_X_FORWARDED_FOR'], you will get 127.0.0.1 (localhost). Logs, analytics, geolocation — everything will break. Worse, the attacker could substitute another user's IP to impersonate them.
Solution: trust the X-Forwarded-For header only if REMOTE_ADDR equals the IP of your trusted proxy. No other way.
Best Practice 2025: A Universal Function for Getting the IP
I have compiled proven logic that takes into account all modern realities. The function:
- Checks if the request is coming through trusted proxies (you specify their list).
- If yes — extracts the first IP from
X-Forwarded-For. - If no — returns
REMOTE_ADDR. - Filters private and reserved addresses (optional).
<?php/** * Gets the real client IP address. * * @param array $trustedProxies List of trusted proxy IPs * @return string IP address or '0.0.0.0' if unable to determine */function getClientIP(array $trustedProxies = []): string { $remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';