How to get CF to know a user’s real IP address, when behind a proxy, load balancer, caching solution, etc.
If your server is behind some loadbalancer, proxy, or caching solution, you may need to know the “real” IP address for a user. Or perhaps you are developer, and because of this same problem, you find that you can never get CF “debugging output” to appear, even though your IP address is in the CF Admin list. See this post, for how to leverage the Tomcat RemoteIPValve, which can be configured easily within CF.
Someone asked how the could get CF to track the “real” ip address for someone, when their CF server was operating behind a particular proxy/caching solution–in their case Cloudflare, but it could happen with any number of such solutions. Often, such proxies/load balancers/caching servers will cause the IP address of that server to show up to CF, not that of the originating user.
Why is this a problem? For one thing, some may expect to see the “real” user ip address in the cgi.remote_addr variable, which perhaps they store in a database or log, but instead they will find what is tracked is the proxy’s ip address. A different challenge is for folks expecting to see CF Admin debugging output (assuming it’s enabled, which is not recommended for production): in this case, even if their originating IP address is in the “allowed ip addresses” list in the CF Admin, they won’t see the debug output because CF sees their request coming from the proxy’s IP address instead.
The good news is that there is a solution for this problem. It takes a combination of knowing first what HEADER is passed from the proxy/cache/load balance server to identify the originating IP address, and then setting up CF to now REGARD THAT HEADER as the one holding the IP address. You can enable it with the Tomcat RemoteIPValve (CF 10 and above run on Tomcat out of the box), as I show below.
To be clear, enabling this change will require that you have the authority to change CF config files, and you will need to restart CF for it to take effect. But anyone can use the info in the first section below, to find out WHAT the header is, and then they could ask someone with authority to make the needed change.
Getting the header with the real IP address
So first, note that most such caching/proxy/load balancer solutions which “change the ip address” to be their own will also send along an “http header” to identify the originating user’s IP address. In most cases, the header name is “X-Forwarded-For”, or perhaps “X-Real-IP”.
In the case of Cloudflare, it also passes it in as “CF-Connecting-IP”. (Here the “CF” stands for Cloudflare, not ColdFusion.) That’s useful, because in some environments, another server between CloudFlare and the origin server (where ColdFusion is installed) might itself set those “standard” headers (to some other unexpected value), and in that case can rely on this CF-Connecting-IP header instead. (For more on these headers from a Cloudflare perspective, see this support page of theirs.)
But next, whatever the header may be, you could find out WHAT headers are coming into a CF page by dumping the result of CF’s gethttprequestdata() function, which shows all incoming headers (and more) passed in to the page (which is showing that dump):
More specifically, see the headers struct within that:
And someone with a CF site behind CloudFlare should see that it does have that header for CF-Connecting-IP, among others. But again look for X-Forwaded-For, X-Real-IP, or others. Basically, use your browser’s “find” feature to locate what you know to be your “real” IP address, and see what header tracks that value.
Once you know, you could also access its value in CFML code with:
and so on.
Changing CF to USE that header for the IP address it tracks
Even so, getting that value (showing it, or storing it into a variable) is not going to solve the problems above, of either someone wanting CF’s cgi.remote_addr to know the value, or to have the CF admin regard the “real” ip address when deciding whether to show debugging output. Instead, you need to get CF to regard THAT header and its value as the IP that CF knows for those purposes.
To do that, you need to implement a change in the underlying Tomcat configuration of CF. You will enable Tomcat’s RemoteIPValve feature, which is designed specifically for this challenge (not unique to CF at all). You will make a one-line change to a file, and restart CF, then test your changes.
Specifically, in the CF folder (such as C:/ColdFusion2018), you will find the cfusion/runtime/conf folder, and in there a server.xml file. Make a copy of it, before changing it, to revert if you have trouble–CF won’t come up if you make certain mistakes in that file. (I will note also that if you may run “multiple instances” of CF, then you will find a runtime/conf folder with its own separate server.xml, in the folder for the name of that instance just under your CF folder. )
In the file, you will find an <engine element. It should look like this:
<Engine name="Catalina" defaultHost="localhost" jvmRoute="cfusion">
(Note that if you are looking at the server.xml of some instance other than the cfusion one, then the jvmroute value will name that instance name. I’m NOT proposing you CHANGE that line at all. I just point it out with regard to what you should be searching for.)
Under that line, you can add this a new line, when enables the valve (at the “engine” level) and names that whatever header you identified in the first section as the header holding the real IP address. In the case of Cloudflare, for instance:
<Valve className="org.apache.catalina.valves.RemoteIpValve" remoteIpHeader="CF-Connecting-IP" />
You may want to make sure there’s not already such a RemoteIpValve in that file first (someone else could have put one there, for use with some other proxy). For instance, one can also enable this valve at the host level, or context level, both under the engine level.
I won’t try to explain that any further here, or if indeed you may need or could want to use any of the various available attributes on that RemoteIPValve, as documented at the Tomcat site:
But the good news is that that single simple line above may be all you need (along with a restart of CF). If anything goes amiss on restart, revert back to the original server.xml and compare it to what you changed, to see where you may have made a mistake.
Handling this IP header detection in your web server, instead of CF
Before concluding, let me address something that some readers may be itching to ask about: couldn’t this be handled instead whatever external web server you may have fronting CF (such as IIS or Apache or nginx)? Can’t those be modified so that the WEB SERVER receives and handles the conversion of the “real” ip header? If so, then that would pass into CF that converted IP address.
Yes, that is possible, in different ways with different web servers. It would also then affect what IP address is tracked in that web server’s access logs, tracking visits to your site/s. I will not elaborate on how to do that in different web servers. There are various resources on that, with varying approaches and value.
For instance, in the case of Cloudflare, I will note they have a support section called “Restoring Visitor IPs“, which shows links to pages discussing how to cause their header to be regarded by various web servers and app servers. Sadly, most either talk about affecting the web server access log only (not the IP address that would be passed on to a backend application server, such as CF).
And there’s indeed a page shown there for configuring Tomcat:
But sadly its focus is on configuring Tomcat to affect what appears in the *Tomcat* access log (which is not even enabled in CF by default, except in CF10). Affecting that log may be interesting for some, but it would not help in the cases described at the opening here. who instead need the remoteipvalve solution above.
Even if changing the web server may somehow suit some better, still other readers may find that they can’t make such a change, but perhaps they CAN change CF (by modifying CF’s Tomcat config), which is why I write the above.
I’ll look forward to hearing what others may have to say. Did I forget anything? Get anything wrong? “As always, I’m just trying to help.”