Citrix NetScaler and Exchange: Case-sensitivity of internal and external URLs

Exchange has known the concept of internal and external URLs for the different services (Outlook Web Access, OAB, EWS, ActiveSync etc) since Exchange 2007. And it’s still confusing people. The internal URL is the URL, that is used to access the desired service from the intranet. The external URL represents the URL that is used to access the service from the internet. Best practice is to use the same URL (the external) for both, use a certificate from a public CA, and use split DNS to access the external domain from the inside of your network.

People tend to imply, that URLs are not case-sensitive. This seems to be true in most cases. The World Wide Web Consortium (W3C) states:

URLs in general are case-sensitive (with the exception of machine names). There may be URLs, or parts of URLs, where case doesn’t matter, but identifying these may not be easy. Users should always consider that URLs are case-sensitive.

Source W3C

Citrix NetScaler and URLs

Citrix NetScaler handles URLs as case-sensitive.

A frequently used concept to load balance Microsoft Exchange with a NetScaler is Content Switching. Policies are used to identify traffic, and actions are used to take action on the traffic that matches the policies. The NetScaler uses the advanced policy engine to create expressions for the Content Switching Policies. When creating a Content Switching policy by creating an expression that uses the CONTAINS operator, you might notice that the results are case-sensitive.

This can be a problem in case of Microsoft Exchange, because /Autodiscover/Autodiscover.xml and /autodiscover/autodiscover.xml, or /ews/exchange.asmx and /EWS/Exchange.asmx are handled different.

Solution

To make sure that different cases are handled, you should add SET_TEXT_MODE(IGNORECASE)  to you policy expression. Citrix describes this in CTX115528.

I’ve changed my NetScaler setup script for Exchange to handle this behavior.

# Replace srv_exchange with the desired server name and ip address
# Add one server object for each of your Exchange servers
add server srv_exchange1 x.x.x.x
add server srv_exchange2 x.x.x.y
# Replace x.x.x.x with the desired IP address
add cs vserver cs_vsrv_exchange SSL x.x.x.x 443 -cltTimeout 180 -caseSensitive OFF
add lb vserver lb_vsrv_exchange_owa SSL 0.0.0.0 0 -persistenceType NONE -cltTimeout 180
add lb vserver lb_vsrv_exchange_ecp SSL 0.0.0.0 0 -persistenceType NONE -cltTimeout 180
add lb vserver lb_vsrv_exchange_ews SSL 0.0.0.0 0 -persistenceType NONE -cltTimeout 180
add lb vserver lb_vsrv_exchange_eas SSL 0.0.0.0 0 -persistenceType NONE -cltTimeout 180
add lb vserver lb_vsrv_exchange_oab SSL 0.0.0.0 0 -persistenceType NONE -cltTimeout 180
add lb vserver lb_vsrv_exchange_rpc SSL 0.0.0.0 0 -persistenceType NONE -cltTimeout 180
add lb vserver lb_vsrv_exchange_mapi SSL 0.0.0.0 0 -persistenceType NONE -cltTimeout 180
add lb vserver lb_vsrv_exchange_autodiscover SSL 0.0.0.0 0 -persistenceType NONE -cltTimeout 180
add cs action cs_action_exchange_owa -targetLBVserver lb_vsrv_owa
add cs action cs_action_exchange_ecp -targetLBVserver lb_vsrv_ecp
add cs action cs_action_exchange_ews -targetLBVserver lb_vsrv_ews
add cs action cs_action_exchange_eas -targetLBVserver lb_vsrv_eas
add cs action cs_action_exchange_oab -targetLBVserver lb_vsrv_oab
add cs action cs_action_exchange_rpc -targetLBVserver lb_vsrv_rpc
add cs action cs_action_exchange_mapi -targetLBVserver lb_vsrv_mapi
add cs action cs_action_exchange_autodiscover -targetLBVserver lb_vsrv_autodiscover
add cs policy cs_pol_exchange_owa -rule "HTTP.REQ.URL.SET_TEXT_MODE(IGNORECASE).CONTAINS(\"/owa\")" -action cs_action_exchange_owa
add cs policy cs_pol_exchange_autodiscover -rule "HTTP.REQ.URL.SET_TEXT_MODE(IGNORECASE).CONTAINS(\"/Autodiscover\")" -action cs_action_exchange_autodiscover
add cs policy cs_pol_exchange_eas -rule "HTTP.REQ.URL.SET_TEXT_MODE(IGNORECASE).CONTAINS(\"/Microsoft-Server-ActiveSync\")" -action cs_action_exchange_eas
add cs policy cs_pol_exchange_ecp -rule "HTTP.REQ.URL.SET_TEXT_MODE(IGNORECASE).CONTAINS(\"/ecp\")" -action cs_action_exchange_ecp
add cs policy cs_pol_exchange_ews -rule "HTTP.REQ.URL.SET_TEXT_MODE(IGNORECASE).CONTAINS(\"/EWS\")" -action cs_action_exchange_ews
add cs policy cs_pol_exchange_mapi -rule "HTTP.REQ.URL.SET_TEXT_MODE(IGNORECASE).CONTAINS(\"/mapi\")" -action cs_action_exchange_mapi
add cs policy cs_pol_exchange_oab -rule "HTTP.REQ.URL.SET_TEXT_MODE(IGNORECASE).CONTAINS(\"/OAB\")" -action cs_action_exchange_oab
add cs policy cs_pol_exchange_rpc -rule "HTTP.REQ.URL.SET_TEXT_MODE(IGNORECASE).CONTAINS(\"/rpc\")" -action cs_action_exchange_rpc
bind cs vserver cs_vsrv_exchange -policyName cs_pol_exchange_owa -priority 100
bind cs vserver cs_vsrv_exchange -policyName cs_pol_exchange_autodiscover -priority 110
bind cs vserver cs_vsrv_exchange -policyName cs_pol_exchange_eas -priority 120
bind cs vserver cs_vsrv_exchange -policyName cs_pol_exchange_ecp -priority 130
bind cs vserver cs_vsrv_exchange -policyName cs_pol_exchange_ews -priority 140
bind cs vserver cs_vsrv_exchange -policyName cs_pol_exchange_mapi -priority 150
bind cs vserver cs_vsrv_exchange -policyName cs_pol_exchange_oab -priority 160
bind cs vserver cs_vsrv_exchange -policyName cs_pol_exchange_rpc -priority 170
add serviceGroup svcgrp_exchange_owa SSL
add serviceGroup svcgrp_exchange_ecp SSL
add serviceGroup svcgrp_exchange_eas SSL
add serviceGroup svcgrp_exchange_ews SSL
add serviceGroup svcgrp_exchange_rpc SSL
add serviceGroup svcgrp_exchange_autodiscover SSL
add serviceGroup svcgrp_exchange_oab SSL
add serviceGroup svcgrp_exchange_mapi SSL
add lb monitor mon_exchange_ecp HTTP-ECV -send "GET /ecp/healthcheck.htm" -recv "200 OK" -LRTM ENABLED -secure YES
add lb monitor mon_exchange_ews HTTP-ECV -send "GET /EWS/healthcheck.htm" -recv "200 OK" -LRTM ENABLED -secure YES
add lb monitor mon_exchange_eas HTTP-ECV -send "GET /Microsoft-Server-ActiveSync/healthcheck.htm" -recv "200 OK" -LRTM ENABLED -secure YES
add lb monitor mon_exchange_oab HTTP-ECV -send "GET /oab/healthcheck.htm" -recv "200 OK" -LRTM ENABLED -secure YES
add lb monitor mon_exchange_rpc HTTP-ECV -send "GET /rpc/healthcheck.htm" -recv "200 OK" -LRTM ENABLED -secure YES
add lb monitor mon_exchange_mapi HTTP-ECV -send "GET /mapi/healthcheck.htm" -recv "200 OK" -LRTM ENABLED -secure YES
add lb monitor mon_exchange_autodiscover HTTP-ECV -send "GET /autodiscover/healthcheck.htm" -recv "200 OK" -LRTM ENABLED -secure YES
add lb monitor mon_exchange_owa HTTP-ECV -send "GET /owa/healthcheck.htm" -recv "200 OK" -LRTM ENABLED -secure YES
# Adjust the number of exchange servers per service group. This example binds two server objects to each service group
bind serviceGroup svcgrp_exchange_owa srv_exchange1 443 -CustomServerID "\"None\""
bind serviceGroup svcgrp_exchange_owa srv_exchange2 443 -CustomServerID "\"None\""
bind serviceGroup svcgrp_exchange_owa -monitorName mon_exchange_owa
bind serviceGroup svcgrp_exchange_ecp srv_exchange1 443 -CustomServerID "\"None\""
bind serviceGroup svcgrp_exchange_ecp srv_exchange2 443 -CustomServerID "\"None\""
bind serviceGroup svcgrp_exchange_ecp -monitorName mon_exchange_ecp
bind serviceGroup svcgrp_exchange_eas srv_exchange1 443 -CustomServerID "\"None\""
bind serviceGroup svcgrp_exchange_eas srv_exchange2 443 -CustomServerID "\"None\""
bind serviceGroup svcgrp_exchange_eas -monitorName mon_exchange_eas
bind serviceGroup svcgrp_exchange_ews srv_exchange1 443 -CustomServerID "\"None\""
bind serviceGroup svcgrp_exchange_ews srv_exchange2 443 -CustomServerID "\"None\""
bind serviceGroup svcgrp_exchange_ews -monitorName mon_exchange_ews
bind serviceGroup svcgrp_exchange_rpc srv_exchange1 443 -CustomServerID "\"None\""
bind serviceGroup svcgrp_exchange_rpc srv_exchange2 443 -CustomServerID "\"None\""
bind serviceGroup svcgrp_exchange_rpc -monitorName mon_exchange_rpc
bind serviceGroup svcgrp_exchange_autodiscover srv_exchange1 443 -CustomServerID "\"None\""
bind serviceGroup svcgrp_exchange_autodiscover srv_exchange2 443 -CustomServerID "\"None\""
bind serviceGroup svcgrp_exchange_autodiscover -monitorName mon_exchange_autodiscover
bind serviceGroup svcgrp_exchange_oab srv_exchange1 443 -CustomServerID "\"None\""
bind serviceGroup svcgrp_exchange_oab srv_exchange2 443 -CustomServerID "\"None\""
bind serviceGroup svcgrp_exchange_oab -monitorName mon_exchange_oab
bind serviceGroup svcgrp_exchange_mapi srv_exchange1 443 -CustomServerID "\"None\""
bind serviceGroup svcgrp_exchange_mapi srv_exchange2 443 -CustomServerID "\"None\""
bind serviceGroup svcgrp_exchange_mapi -monitorName mon_exchange_mapi
bind lb vserver lb_vsrv_exchange_owa svcgrp_exchange_owa
bind lb vserver lb_vsrv_exchange_ecp svcgrp_exchange_ecp
bind lb vserver lb_vsrv_exchange_eas svcgrp_exchange_eas
bind lb vserver lb_vsrv_exchange_ews svcgrp_exchange_ews
bind lb vserver lb_vsrv_exchange_rpc svcgrp_exchange_rpc
bind lb vserver lb_vsrv_exchange_autodiscover svcgrp_exchange_autodiscover
bind lb vserver lb_vsrv_exchange_oab svcgrp_exchange_oab
bind lb vserver lb_vsrv_exchange_mapi svcgrp_exchange_mapi
# Replace cert-key-pair with the desired SSL key-pair name
bind ssl vserver lb_vsrv_exchange_ecp -certkeyName cert-key-pair
bind ssl vserver lb_vsrv_exchange_ews -certkeyName cert-key-pair
bind ssl vserver lb_vsrv_exchange_eas -certkeyName cert-key-pair
bind ssl vserver lb_vsrv_exchange_oab -certkeyName cert-key-pair
bind ssl vserver lb_vsrv_exchange_owa -certkeyName cert-key-pair
bind ssl vserver lb_vsrv_exchange_rpc -certkeyName cert-key-pair
bind ssl vserver lb_vsrv_exchange_mapi -certkeyName cert-key-pair
bind ssl vserver lb_vsrv_exchange_autodiscover -certkeyName cert-key-pair
bind ssl vserver cs_vsrv_exchange_exchange -certkeyName cert-key-pair
set ssl vserver lb_vsrv_exchange_ecp -ssl3 DISABLED
set ssl vserver lb_vsrv_exchange_ews -ssl3 DISABLED
set ssl vserver lb_vsrv_exchange_eas -ssl3 DISABLED
set ssl vserver lb_vsrv_exchange_oab -ssl3 DISABLED
set ssl vserver lb_vsrv_exchange_owa -ssl3 DISABLED
set ssl vserver lb_vsrv_exchange_rpc -ssl3 DISABLED
set ssl vserver lb_vsrv_exchange_mapi -ssl3 DISABLED
set ssl vserver lb_vsrv_exchange_autodiscover -ssl3 DISABLED