Skip to content

Load Balance Mideye Instances with Citrix ADC (NetScaler)

This guide describes how to configure RADIUS load balancing on Citrix ADC (NetScaler) to distribute authentication requests across multiple Mideye Server nodes for high availability and resilience.

Use this configuration when you have two or more Mideye Server nodes and want:

  • High availability — if one Mideye Server is unreachable, traffic fails over to the remaining nodes
  • Load distribution — spread authentication requests across nodes
  • Health monitoring — the ADC probes each Mideye Server and removes unhealthy nodes automatically

User → Gateway Virtual Server
RADIUS Policy → RADIUS Action (points to LB VIP)
LB Virtual Server (RADIUS, SOURCEIP persistence)
↓ ↓
Mideye_Server1_svc Mideye_Server2_svc
(172.16.0.100:1812) (172.16.0.101:1812)
↓ ↓
RADIUS Monitor RADIUS Monitor

The Gateway’s RADIUS action points to the LB Virtual Server IP instead of a single Mideye Server IP. The LB Virtual Server distributes requests to the RADIUS services and uses SOURCEIP persistence to ensure that all packets within a RADIUS transaction reach the same backend.

RADIUS response path — source IP matters

Section titled “RADIUS response path — source IP matters”

Correct flow — response returns through the LB:

VPN ──Access-Request──→ LB VIP (172.16.3.199) ──→ Mideye (172.16.0.100)
VPN ←─Access-Accept──── LB VIP (172.16.3.199) ←── Mideye (172.16.0.100)
✓ Source IP matches

Broken flow — Mideye responds directly (asymmetric routing):

VPN ──Access-Request──→ LB VIP (172.16.3.199) ──→ Mideye (172.16.0.100)
VPN ←─Access-Accept──── Mideye (172.16.0.100) ←─┘ (bypasses LB)
✗ Source IP mismatch — VPN drops the packet

This happens when the Mideye Server has a direct route to the VPN that does not pass through the ADC. The return path must go back through the load balancer so the ADC rewrites the source IP to the VIP.

The solution depends on your network topology:

  1. Use SNIP as the source (recommended) — Ensure the ADC uses a Subnet IP (SNIP) on the same network as the Mideye Servers to forward RADIUS requests. Mideye sees the SNIP as the source and responds back to it, ensuring the response traverses the ADC. This is the default NetScaler behavior when the SNIP and backend servers are on the same subnet.

  2. Use Use Source IP (USIP) mode = NO (default) — By default, the ADC replaces the client’s source IP with its own SNIP when forwarding to the backend. Do not enable USIP on the RADIUS service — if you do, Mideye sees the VPN’s IP as the source and may route the response directly back, bypassing the LB.

  3. Static route on Mideye Servers — If the Mideye Servers are on a different subnet, add a static route on each Mideye Server so that traffic destined for the VPN’s subnet routes through the ADC’s SNIP as the gateway.

  4. Verify with a packet capture — If authentication silently fails, capture traffic on the Mideye Server to confirm whether responses are going back to the ADC (SNIP) or directly to the VPN:

    Terminal window
    # On the Mideye Server — watch RADIUS traffic
    tcpdump -i any udp port 1812 -nn

    You should see requests arriving from the ADC SNIP and responses going to the ADC SNIP — not directly to the VPN.


ComponentSupported versions
NetScaler / Citrix ADC12.x, 13.x, 14.x (current)
Feature requiredLoad Balancing (must be enabled)
Mideye Server4.3.0+ (5.x+ recommended)

  • Load Balancing feature enabled on the ADC
  • At least two Mideye Server nodes configured with RADIUS
  • A RADIUS client in each Mideye Server for the ADC IP
  • A dedicated IP address on the ADC for the LB Virtual Server (VIP)
  • Shared secret must be the same for all Mideye Servers behind the LB VIP

Terminal window
enable ns feature LoadBalancing

Verify:

Terminal window
show ns feature

Look for Load Balancing with status ON.


Create a server object for each Mideye Server node.

Terminal window
add server Mideye_Server1 172.16.0.100
add server Mideye_Server2 172.16.0.101

Create a RADIUS service for each server object. A service binds a server to a port and protocol.

Terminal window
add service Mideye_Server1_svc Mideye_Server1 RADIUS 1812
add service Mideye_Server2_svc Mideye_Server2 RADIUS 1812

A RADIUS monitor periodically sends an authentication request to each Mideye Server to verify it is responding. The monitor uses a test account — this account does not need to exist in Mideye and will intentionally fail authentication.

Terminal window
add lb monitor Mideye_RADIUS_mon RADIUS \
-respCode 3 \
-userName fake_monitor_user \
-password fake_password \
-radKey <shared-secret> \
-LRTM DISABLED \
-retries 1 \
-interval 30 \
-resptimeout 10 \
-destPort 1812
ParameterValueNotes
-respCode3Access-Reject — server is alive but user is invalid (expected)
-userNamefake_monitor_userFake user for health probes (will appear in Mideye logs)
-passwordfake_passwordFake password
-radKeyShared secretMust match the Mideye RADIUS client configuration
-LRTMDISABLEDDisable Least Response Time Method for monitor
-retries1Number of retries before marking DOWN
-interval30Probe interval in seconds
-resptimeout10Timeout for each probe

If Mideye Server exposes a health API endpoint, you can use an HTTP-ECV or HTTPS monitor instead of a RADIUS monitor. This avoids the authentication log noise.

See Server Monitoring for Mideye health API details and SSL service monitoring in the NetScaler documentation.


Replace the default ping monitor with the RADIUS monitor on each service.

Terminal window
# Unbind the default ping monitor
unbind service Mideye_Server1_svc -monitorName ping
unbind service Mideye_Server2_svc -monitorName ping
# Bind the RADIUS monitor
bind service Mideye_Server1_svc -monitorName Mideye_RADIUS_mon
bind service Mideye_Server2_svc -monitorName Mideye_RADIUS_mon

After binding, verify that the services show state UP — this means the RADIUS monitor is receiving the expected response code from Mideye Server.


Create a RADIUS LB Virtual Server with SOURCEIP persistence. This ensures that all RADIUS packets within a single authentication transaction (Access-Request → Access-Challenge → Access-Request) reach the same Mideye Server backend.

Terminal window
add lb vserver Mideye_LB_vsrv RADIUS 172.16.3.199 1812 \
-persistenceType SOURCEIP \
-timeout 120 \
-cltTimeout 120
ParameterValueNotes
ProtocolRADIUS
IP Address172.16.3.199Dedicated VIP on the ADC
Port1812
-persistenceTypeSOURCEIPRequired for RADIUS MFA — ensures challenge-response stickiness
-timeout120Persistence timeout in seconds
-cltTimeout120Client idle timeout

Terminal window
bind lb vserver Mideye_LB_vsrv Mideye_Server1_svc
bind lb vserver Mideye_LB_vsrv Mideye_Server2_svc

After binding, refresh the Virtual Servers page — the state should show UP with green indicators if both services are healthy.


8. Use the LB VIP in your RADIUS authentication action

Section titled “8. Use the LB VIP in your RADIUS authentication action”

Now update (or create) your Gateway RADIUS action to point to the LB Virtual Server VIP instead of a single Mideye Server IP:

Terminal window
# If updating an existing action:
set authentication radiusAction Mideye_RADIUS \
-serverIP 172.16.3.199
# Or create a new action pointing to the LB VIP:
add authentication radiusAction Mideye_RADIUS_LB \
-serverIP 172.16.3.199 \
-serverPort 1812 \
-authTimeout 35 \
-radKey <shared-secret> \
-radNASip DISABLED \
-authservRetry 1 \
-passEncoding pap

Then follow the steps in the RADIUS Authentication guide to create a policy and bind it to your Gateway Virtual Server (if not already done).


Terminal window
save ns config

Here is the full configuration for a two-node Mideye Server setup:

Terminal window
# Enable load balancing
enable ns feature LoadBalancing
# Server objects
add server Mideye_Server1 172.16.0.100
add server Mideye_Server2 172.16.0.101
# RADIUS services
add service Mideye_Server1_svc Mideye_Server1 RADIUS 1812
add service Mideye_Server2_svc Mideye_Server2 RADIUS 1812
# RADIUS health monitor
add lb monitor Mideye_RADIUS_mon RADIUS \
-respCode 3 \
-userName fake_monitor_user \
-password fake_password \
-radKey <shared-secret> \
-LRTM DISABLED \
-retries 1 \
-interval 30 \
-resptimeout 10
# Bind monitor to services (replace default ping)
unbind service Mideye_Server1_svc -monitorName ping
unbind service Mideye_Server2_svc -monitorName ping
bind service Mideye_Server1_svc -monitorName Mideye_RADIUS_mon
bind service Mideye_Server2_svc -monitorName Mideye_RADIUS_mon
# LB Virtual Server with SOURCEIP persistence
add lb vserver Mideye_LB_vsrv RADIUS 172.16.3.199 1812 \
-persistenceType SOURCEIP \
-timeout 120 \
-cltTimeout 120
# Bind services to LB Virtual Server
bind lb vserver Mideye_LB_vsrv Mideye_Server1_svc
bind lb vserver Mideye_LB_vsrv Mideye_Server2_svc
# RADIUS authentication action (pointing to LB VIP)
add authentication radiusAction Mideye_RADIUS \
-serverIP 172.16.3.199 \
-serverPort 1812 \
-authTimeout 35 \
-radKey <shared-secret> \
-radNASip DISABLED \
-authservRetry 1 \
-passEncoding pap
# RADIUS authentication policy
add authentication radiusPolicy Mideye_RADIUS_pol ns_true Mideye_RADIUS
# Bind policy to Gateway Virtual Server
bind vpn vserver my_gateway -policy Mideye_RADIUS_pol -priority 100
# Save
save ns config

Terminal window
# Check service states (should show UP)
show service Mideye_Server1_svc
show service Mideye_Server2_svc
# Check LB Virtual Server state and bound services
show lb vserver Mideye_LB_vsrv
# Check monitor status
show lb monitor Mideye_RADIUS_mon
# Check statistics
stat lb vserver Mideye_LB_vsrv

SymptomCheck
Authentication silently fails — no error, no rejectRADIUS source-IP mismatch. Mideye is responding directly to the VPN instead of through the LB. The VPN drops the response because the source IP is Mideye’s IP, not the LB VIP. See RADIUS response path above. Run tcpdump -i any udp port 1812 -nn on Mideye to verify the response destination.
Service state shows DOWNMonitor is not receiving expected response code. Verify shared secret, response code (3), and RADIUS connectivity.
LB Virtual Server shows DOWNAll bound services are DOWN. Fix individual service health first.
Authentication works but only hits one serverSOURCEIP persistence is working as designed — requests from the same client IP go to the same backend.
Challenge-Response fails intermittentlyVerify SOURCEIP persistence is configured on the LB Virtual Server. Without it, the OTP response may reach a different backend.
Monitor floods Mideye logsNormal — the monitor sends periodic probes. Adjust -interval to reduce frequency, or use an HTTP health check instead.
fake_monitor_user errors in Mideye logsExpected. The monitor intentionally sends invalid credentials to verify the server is responsive.