Skip to main content

Tutorial: Track Real Client IP Addresses in Access Server Behind a Proxy, Load Balancer, or Cloudflare

Abstract

Learn how to verify real client IP addresses in Access Server when deployed behind a proxy, reverse proxy, load balancer, or Cloudflare — covers X-Forwarded-For, CF-Connecting-IP, and configuring a custom HTTP header for the proxied IP.

Overview

When you deploy Access Server behind a proxy, reverse proxy, or load balancer, Access Server receives the web request from that intermediary rather than directly from the client.

Starting with Access Server 3.2.2, you can identify both addresses associated with these web requests:

  • Request IP: The IP address from which Access Server directly receives the request. In a proxied deployment, this is typically the IP address of the proxy, reverse proxy, or load balancer facing Access Server.

  • Proxied IP: The real client IP address, or original client IP, supplied in a supported HTTP header by the proxy, reverse proxy, or load balancer.

Access Server detects X-Forwarded-For and CF-Connecting-IP by default. CF-Connecting-IP is the header Cloudflare injects to identify the original visitor IP address. You can also configure another header, such as X-Real-IP, when your infrastructure uses a different header for the client address.

This tutorial shows you how to verify the Request IP and Proxied IP in the Activity Log and Access Server logs, test header detection with the Web API, and configure a custom proxied-IP header.

Tracking the original client IP is especially useful for auditing and troubleshooting production secure remote access deployments that place Access Server behind a proxy or load balancer.

Prerequisites

Before you begin, ensure you have:

  • Access Server 3.2.2 or newer.

  • Admin Web UI access.

  • Console access and the ability to get root access.

  • An administrator account configured in Access Server.

  • A proxy, reverse proxy, or load balancer for Step 1.

  • Optional: A remote computer from which you can run Web API requests for Steps 2 and 3.

Note

In our documentation, we use example IPv4 addresses and subnets reserved for documentation, such as 192.0.2.0/24, 198.51.100.0/24, and 203.0.113.0/24.

Ensure you replace them with valid IPv4 addresses and subnets for your network(s).

How Access Server detects X-Forwarded-For and CF-Connecting-IP

By default, Access Server can detect the original client IP from these HTTP request headers:

  • X-Forwarded-For

  • CF-Connecting-IP

A proxy or load balancer can add one of these headers before forwarding a request to Access Server.

For example:

Client
192.0.2.5
   |
   v
Proxy / Load Balancer
198.51.100.5
   |
   | X-Forwarded-For: 192.0.2.5
   v
Access Server

Access Server can then record:

Request IP: 198.51.100.5
Proxied IP: 192.0.2.5

Important

HTTP forwarding headers can be supplied by a client or a proxy. Treat the Proxied IP as trustworthy only when your network architecture ensures that Access Server receives these requests through a trusted proxy, reverse proxy, or load balancer.

Don't use a forwarded IP address by itself as proof of client identity.

Use this procedure when Access Server is already behind a proxy, reverse proxy, or load balancer that adds the original client IP to the web request.

Verify the addresses in the Activity Log

  1. From a client device, access the Admin Web UI through the proxy, reverse proxy, or load balancer.

  2. Sign in to the Admin Web UI.

  3. Navigate to Status → Activity Log.

  4. Locate the WEB_ADMIN login activity you just generated.

  5. Review Request IP.

    • This should display the address from which Access Server directly received the request, which is typically the proxy, reverse proxy, or load balancer IP.

  6. Review Proxied IP.

    • This should display the real client IP address supplied in the forwarded HTTP header.

  7. Optional: Sign in to the Client Web UI.

  8. Locate the corresponding WEB_CLIENT activity in the Activity Log.

  9. Verify its Request IP and Proxied IP values.

Verify the addresses in the Access Server log

  1. From the client device, sign in to the Admin Web UI through the proxy, reverse proxy, or load balancer.

  2. Connect to the console and get root privileges.

  3. Review:

    /var/log/openvpnas.log
  4. Locate the WEB_ADMIN request corresponding to your login.

    • Example:

      2026-07-29T21:05:59+0000 [stdout#info] [WEB] OUT: '2026-07-29T21:05:59+0000 [twisted.python.log#info] "198.51.100.5 [192.0.2.5]" - - [29/Jul/2026:21:05:59 +0000] "POST /api/auth/login/userpassword HTTP/2" 200 460 "-"
    • In this example:

      Request IP: 198.51.100.5

      Proxied IP: 192.0.2.5

  5. Confirm that the Request IP matches the proxy-facing address.

  6. Confirm that the Proxied IP matches the original client IP.

Expected result: The Activity Log and Access Server log show the intermediary address as the Request IP and the original client address as the Proxied IP.

Use this procedure to test Access Server's proxied-IP detection without configuring a proxy or load balancer first.

The example manually sends an X-Forwarded-For header to simulate the header a proxy would normally add.

Tip

This tutorial uses cURL to call the Access Server Web API. You can also use the embedded Swagger UI. Refer to Tutorial: Test the New Web API (OpenAPI) in Access Server 3.0.

  1. Connect to the console and get root privileges.

  2. Run the following API call from a remote machine to generate web request traffic to your Access Server:

    curl -X 'POST' 'https://203.0.113.51/api/auth/login/userpassword' -H 'accept: application/json'  -H 'Content-Type: application/json' -H "X-Forwarded-For: 192.0.2.52" -d '{"username": "<ADMIN_USERNAME>3","password": "<ADMIN_PASSWORD>4", "request_admin":"true"}' -k -v

    1

    Replace 203.0.113.5 with your Access Server address.

    2

    Replace 192.0.2.5 with the test Proxied IP.

    3

    Replace <ADMIN_USERNAME> with your administrator username.

    4

    Replace <ADMIN_PASSWORD> with the administrator password.

    Important

    Supplying passwords directly on the command line can expose them through shell history or the process list. Use test credentials and protect or remove command history as appropriate for your environment.

  3. Review the verbose cURL output.

  4. Locate X-Forwarded-For: 192.0.2.5.

    • Example output on the client side confirming that the request included the x-Forwarded-For HTTP header:

      > POST /api/auth/login/userpassword HTTP/2
      > Host: 203.0.113.5
      > User-Agent: curl/8.5.0
      > accept: application/json
      > Content-Type: application/json
      > X-Forwarded-For: 192.0.2.5
      > Content-Length: 50
    • Example output on the Access Server side confirming the Request IP is 198.51.100.5 and the Proxied IP is 192.0.2.5:

      2026-07-29T23:49:01+0000 [stdout#info] [WEB] OUT: '2026-07-29T23:49:01+0000 [twisted.python.log#info] "198.51.100.5 [192.0.2.5]" - - [29/Jul/2026:23:49:00 +0000] "POST /api/auth/login/userpassword HTTP/2" 200 460 "-" "curl/8.5.0"'

Access Server detects X-Forwarded-For and CF-Connecting-IP by default.

If your proxy, reverse proxy, or load balancer uses another HTTP header for the original client IP, configure Access Server to detect that header instead.

The following example uses X-Real-IP as our custom HTTP header to inject the real/original client IP address. Replace it with one of your preference.

  1. Connect to the console and get root privileges.

  2. Configure X-Real-IP as the proxied-IP header:

    sacli -k "cs.proxied_ip_header" -v "X-Real-IP" ConfigPut
  3. Restart Access Server:

    sacli start
  4. From a remote computer, run the following to test:

    curl -X 'POST' 'https://203.0.113.5/api/auth/login/userpassword' -H 'accept: application/json'  -H 'Content-Type: application/json' -H "X-Real-IP: 192.0.2.5" -d '{"username": "<ADMIN_USERNAME>","password": "<ADMIN_PASSWORD>", "request_admin":"true"}' -k -v
  5. Replace the Access Server address and credentials with values for your environment.

  6. Review the verbose cURL output.

  7. Confirm the header output:

    • Example output on the client side confirming that it contains the X-Real-IP HTTP header:

      > POST /api/auth/login/userpassword HTTP/2
      > Host: 203.0.113.5
      > User-Agent: curl/8.5.0
      > accept: application/json
      > Content-Type: application/json
      > X-Real-IP: 192.0.2.5
      > Content-Length: 50
    • Example output on the Access Server side confirming the Request IP is 198.51.100.5 and the Proxied IP is 192.0.2.5:

      2026-07-29T23:49:01+0000 [stdout#info] [WEB] OUT: '2026-07-29T23:49:01+0000 [twisted.python.log#info] "198.51.100.5 [192.0.2.5]" - - [29/Jul/2026:23:49:00 +0000] "POST /api/auth/login/userpassword HTTP/2" 200 460 "-" "curl/8.5.0"'

See also