We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
Retrieving a clientâs external IP address in a Phoenix application can be helpful for logging, security, and analytics purposes. If your app is behind a reverse proxy (like a load balancer or Nginx), the client IP may not be directly accessible, as the proxy might handle requests on behalf of the client. However, many proxies include the client IP in request headers, which Phoenix can use to reliably fetch the clientâs IP address.
In this post, weâll cover how to retrieve the clientâs IP address from the request headers and display it in a Phoenix application.
Why use request headers?
When an application is deployed behind a reverse proxy, the clientâs IP address might be masked by the proxy's IP. To address this, reverse proxies typically add headers like X-Forwarded-For
, X-Real-IP
, or Forwarded
that carry the original IP address of the client.
Using these headers allows us to avoid external services to obtain the IP, making the process faster and more secure.
Step 1: retrieve the IP address from request headers
The Phoenix Plug.Conn
struct provides functions for accessing request headers. Hereâs a simple way to implement a helper function that checks for the client IP in headers, falling back to conn.remote_ip
if headers are absent or untrusted.
Letâs create a helper function, get_client_ip/1
, that will:
- Check the
X-Forwarded-For
,X-Real-IP
, andForwarded
headers for the client IP in that order. - If none of these headers are found, fall back to
conn.remote_ip
, which contains the IP from the immediate client (usually the reverse proxy if youâre behind one). - Parse and return the first valid IP address found.
Hereâs how to write it:
defmodule MyAppWeb.PageController do
use MyAppWeb, :controller
def index(conn, _params) do
client_ip = get_client_ip(conn)
render(conn, "index.html", client_ip: client_ip)
end
defp get_client_ip(conn) do
# Check the most common headers for client IP in order of priority
conn
|> get_forwarded_ip()
|> case do
nil -> conn.remote_ip |> :inet_parse.ntoa() |> to_string()
ip -> ip
end
end
defp get_forwarded_ip(conn) do
# Try headers typically set by proxies
forwarded_for = List.first(get_req_header(conn, "x-forwarded-for"))
real_ip = List.first(get_req_header(conn, "x-real-ip"))
forwarded = List.first(get_req_header(conn, "forwarded"))
cond do
forwarded_for -> String.split(forwarded_for, ",") |> List.first() |> String.trim()
real_ip -> real_ip
forwarded && Regex.run(~r/for=(?<ip>[^\s;]+)/, forwarded, capture: :all_but_first) -> List.first(Regex.run(~r/for=(?<ip>[^\s;]+)/, forwarded))
true -> nil
end
end
end
This code performs the following:
- The
get_forwarded_ip/1
function extracts the first IP address it finds in the headersX-Forwarded-For
,X-Real-IP
, orForwarded
. - If no IP is found in the headers,
get_client_ip/1
falls back toconn.remote_ip
, which retrieves the IP of the last immediate client in the request chain.
Step 2: display the IP address in the view
Now that we have the clientâs IP address, letâs display it in the view. In index.html.eex
, you can add a simple display for the IP address:
<%= if @client_ip do %>
<p>Your IP address is: <%= @client_ip %></p>
<% else %>
<p>Could not retrieve your IP address.</p>
<% end %>
Step 3: testing and verification
Local testing
If youâre testing locally, you might not see headers like X-Forwarded-For
, especially if youâre not behind a proxy. In this case, conn.remote_ip
will return your local IP (e.g., 127.0.0.1
), which is expected behavior.
Production setup
In production, ensure that your reverse proxy (such as Nginx, AWS ELB, or Herokuâs router) is configured to forward the client IP through headers. This setup typically involves enabling the X-Forwarded-For
or X-Real-IP
header.
For example, with Nginx, you can configure it as follows:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
Security considerations
Headers like X-Forwarded-For
can be spoofed, as clients can send arbitrary values. For security purposes, ensure that these headers are only trusted from requests that come through your reverse proxy. Many reverse proxies allow you to configure this behavior, restricting which IP addresses or ranges can set these headers.
Trying it out
If you want to try it out, you can use this test page.
Conclusion
Retrieving the clientâs IP address in a Phoenix app is straightforward once you know how to handle the relevant headers. By examining X-Forwarded-For
, X-Real-IP
, and Forwarded
headers in order, and using conn.remote_ip
as a fallback, you can reliably capture the client IP without needing an external service.
Using this approach can enhance your logging, security monitoring, and analytics without adding extra dependencies or latency.
If this post was enjoyable or useful for you, please share it! If you have comments, questions, or feedback, you can email my personal email. To get new posts, subscribe use the RSS feed.