Someone asks why a VPN user shows up in the application log with an address that belongs to the gateway, or with an address that changed after lunch. The answer starts with what the value is: an entry the web server placed in the request environment. It is not a query into the tunnel, not a certificate identity, and not a promise that the number belongs to one person. Reading it as any of those three produces confident answers that are wrong in different ways.

The value comes from the server, not the tunnel

PHP receives the request environment from whatever executed the script. The PHP server-variable reference describes these entries as created by the web server, with availability that depends on how the request arrived.

A script run as a VPN command-line hook is a different execution context entirely. It has no web request, no client socket and no REMOTE_ADDR, which is why copying web code into a hook produces empty values rather than wrong ones. The hook has its own environment, populated by the daemon, and it is documented separately. Reaching for REMOTE_ADDR there is reaching for a variable that was never set.

Keep that distinction in mind before you compare two logs. You may be comparing a page request with a hook, which is not a comparison at all. Check the execution context first and the address second.

Walk the request path before trusting the number

The reported address is the peer of whichever connection the web server saw. Insert anything between the client and that server and the peer changes. Translation, termination and balancing each move the observation one hop further from the user.

Four common paths produce four different observations.

Path to the listenerWhat can be reported
Client routed straight to the private listener, no translationThe client's tunnel address
Gateway translates the source before forwardingThe gateway's translated address
Reverse proxy terminates the client and opens its own backend connectionThe proxy's backend address
Load balancer in front of two proxiesWhichever hop the server treats as its peer

Write the path down with the destination name, the address family and every translation point. Without that map, a correct value and a wrong value look identical in a log line. Two lines of diagram save an afternoon of argument.

Replacement only happens where it is configured

A server or framework can deliberately substitute the client address. Apache's remote-IP module and NGINX's real-IP module both do this, and the value PHP receives depends on that configuration plus the parameters passed to the script handler.

So REMOTE_ADDR is not universally the untouched TCP peer. It is the peer as the server chose to report it after applying its own policy. That distinction decides whether the value is evidence or decoration.

Ask the server owner which field is authoritative in this deployment and how the original peer is preserved when replacement happens. If nobody can answer, the log is not evidence of anything yet.

Do not read forwarding headers on trust

A caller can send forwarding headers itself. Treating the first comma-separated entry as a verified user address is a decision to believe whoever connected, which is the opposite of what the header is for. The header describes a chain, and only a trusted layer can vouch for the chain.

Use the server or framework's trusted-proxy support with the actual proxy chain configured, and let it discard values from sources outside that chain. Hand-rolled parsing is where most of the interesting failures live.

A proxy-reported address also does not change the packet source an upstream firewall sees. Keep HTTP metadata and network filtering as separate observations, because they answer different questions.

Use application identity for decisions

For access to a user's records, rely on the authenticated principal and the application's authorization rules. An inner address can be reassigned, and one translated address can stand for hundreds of concurrent users. Neither property is something an address check can repair.

If operations need to tie a request to a VPN session, join the observed address and timestamp to authoritative session records under the translation model you documented. That produces an association you can defend; an address alone does not.

For diagnosis, log the few fields you need rather than the whole server array, which can carry headers and authentication material. The useful conclusion states what this value means in this deployment, not which value looks most like a client.

Erthyglow kevrennek