Writing a PAC File

This section describes how to write a new PAC file from scratch. Zscaler highly recommends that you do the following:

  1. Copy and paste the default PAC file and customize it as necessary.
  2. Build your PAC file one element at a time.
  3. Save the file and test it after each addition.

Before you begin, ensure that you review Best Practices for Writing PAC Files.

FindProxyForURL Function

A PAC file must start with the opening function FindProxyForURL(url,host) on the first line. This function identifies which URLs, host names, or IP addresses are redirected to a proxy. The URL variable must contain the full URL of a destination (e.g., http://www.zscaler.com), and the host variable must contain a domain name or its IP address (e.g., zscaler.com or 72.249.144.174). Generally speaking, the host variable is preferable to the URL variable. If no URL or host is specified, all web requests will use the return argument.

Return Statements

Enter a return statement in curly brackets after each argument. The return statement directs requests to a proxy or to the specified destination. For example:

functionFindProxyForURL(url, host)
{ return "PROXY ${GATEWAY}:9400"; return "DIRECT"; }

A return statement accepts one of two values:

  • DIRECT tells the browser to bypass the proxy and go directly to the destination server.
  • PROXY tells the browser to send the request to a proxy.

The DIRECT or PROXY values must be in uppercase characters and enclosed in quotation marks. Add a semicolon immediately after the closing quotation marks. For example:

return "DIRECT";
return "PROXY ${GATEWAY}:9400";

Each PROXY statement must specify the fully qualified host name or the IP address of the proxy and the port. IP addresses are generally discouraged because they can change at any time. Zscaler recommends that you use the variables ${GATEWAY} and ${SECONDARY_GATEWAY} instead. If your organization uses a subcloud, use the variables ${GATEWAY.<Organization name>.zscaler.net} and ${SECONDARY_GATEWAY.<Organization name>.zscaler.net}.

The Zscaler service uses its geo-location technology to automatically find the ZEN closest to you and with the quickest response time. Naming a primary and secondary gateway provides failover in case one of the ZENs is unavailable for any reason.

ZENs accept web requests on ports 80, 443, 9400, 9480 and 9443.

  • Port 80 is the standard port used by almost all web servers.
  • Port 443 is the standard port used for encrypted (HTTPS) traffic. Port 9400 can be used instead, if another host between the end user and ZEN attempts to redirect the user’s traffic before it can reach the ZEN.
  • Port 9443 can be used for road warriors who need the service to proxy and inspect HTTPS transactions.

Write the opening function FindProxyForURL (url, host) { } followed by a return statement on the next line. For example:

functionFindProxyForURL(url, host)
{ return "PROXY ${GATEWAY}:9400"; return "DIRECT"; }

Save the text file and test it. Verify that the PAC file sends your browser to the Zscaler service with no other arguments, ‘if’ statements, or other elements. Confirm by navigating to ip.zscaler.com, which indicates if you reached the web server directly or through a ZEN.

Adding Arguments

You can add various arguments that identify which internal or external hosts must be proxied. Add one nested argument at a time, and then test and confirm that the PAC file works properly after each addition.

Following is a sample PAC file that contains arguments, each of which is preceded by a comment.

functionFindProxyForURL(url, host)
{
//
//Exclude FTP from proxy
//
if (url.substring(0, 4) == "ftp:")
{
return "DIRECT";
}
//
//Bypass proxy for internal hosts
//
if (isInNet(host, "0.0.0.0", "255.0.0.0")||
isInNet(host, "10.0.0.0", "255.0.0.0") ||
isInNet(host, "127.0.0.0", "255.0.0.0") ||
isInNet(host, "169.254.0.0", "255.255.0.0") ||
isInNet(host, "172.16.0.0", "255.240.0.0") ||
isInNet(host, "192.0.2.0", "255.255.255.0")||
isInNet(host, "64.206.157.136", "255.255.255.255"))
{
return "DIRECT";
}
//
//Bypass proxy for this server
//
if (isInNet(host.mail.domain.com)
{
return “DIRECT”;
}
return "PROXY ${GATEWAY}:9400; ${SECONDARY_GATEWAY}:9400; DIRECT";
}

For the arguments in the sample PAC file above, specify the following:

Zscaler-Specific Variables

Listed below are the different Zscaler-specific variables you can use in your PAC file argument:

The following lines exclude FTP traffic from being redirected to the proxy (since the service does not support native FTP):

   //
   //Exclude FTP from proxy
   //
if (url.substring(0, 4) == "ftp:") 
    {
     return "DIRECT";
    }

The argument begins with a simple if statement using the built-in function called url.substring. If arguments are always followed by opening and closing parentheses—they describe the conditions for which the argument must be evaluated and for which a result is required. In the example, the argument specifies that if the URL contains the string ftp: in the first four characters of the URL (from 0 to 4), return DIRECT—bypass the proxy named in the line below. Note that the if argument’s result must be enclosed within its own set of open and closed curly brackets.

Save this change to the PAC file and upload it to the service portal. Reload the PAC file in your browser and navigate to an FTP download site such as ftp://ftp.hp.com/. After loading this page, log in to the Analytics portal to determine if this transaction was logged. The transaction should not appear in the logs.

The following lines in the PAC file example exclude requests for internal hosts from being redirected to a proxy.

//
 //Do not send traffic to the following network to Zscaler
 //
if (isInNet(host), "192.168.0.0", "255.255.0.0")
{
 return "DIRECT";
}

This argument uses the JavaScript function IsInNet(), which is typically used to identify either of the following:

  • Client IP address (If the request comes from this IP address, use this proxy.) Be aware that this argument returns the first IP address on your device, based on its OS. The first IP address, shown when you use the ipconfig command, may be the IPv6 address of the device or the IP address of virtual adapters and this can cause conflicts.
  • Host server IP address (If the request is going to this address, use this proxy.) Be aware that this argument results in a DNS lookup. It can impact performance if the DNS server is not available. Instead, you can use the following to constrain the IsInNet() function based on the host domains being accessed:
If dnsDomainIs(host,”internal.net”) {
If (isInNet(host,”10.0.0.0”,”255.0.0.0”)
Return “DIRECT”;
}

Save the PAC file and test again. Browse to an internal host and ensure that you can reach it. (If you were proxied through a ZEN, your request would be denied. The request first goes outside your network to the Zscaler proxy but is then blocked as it tries to access an internal host as it comes back in from outside the network.)

The following lines in the PAC file example exclude requests for multiple internal hosts from being redirected to a proxy.

  //
  //Bypass proxy for internal hosts
  //
 if(isInNet(host, "0.0.0.0", "255.0.0.0")||
 isInNet(host, "10.0.0.0", "255.0.0.0") ||
 isInNet(host, "127.0.0.0", "255.0.0.0") ||
 isInNet(host, "169.254.0.0", "255.255.0.0") ||
 isInNet(host, "172.16.0.0", "255.240.0.0") ||
 isInNet(host, "192.0.2.0", "255.255.255.0")||
 isInNet(host, "64.206.157.136", "255.255.255.255"))
 {
  return "DIRECT";
 }

 As shown in the example, before closing the IF argument with a final parenthesis, the Boolean “or” operator (||) is used to concatenate the internal networks. The last isInNet statement does not require the or (||) operator. Instead, it uses an additional parenthesis to close the opening parenthesis.

The last statement is a specific IP address, not a network. To bypass specific hosts, write the exact IP address with a subnet mask of 255.255.255.255.

Use of isInNet() is extremely effective when the host you are trying to reach is an actual IP address. If you try to reach a host by domain name, such as https://zscaler.com your browser must perform a DNS lookup for it. If the host name is not resolvable, the client needs to wait for DNS to time out before moving on. To avoid this and to avoid placing an undue strain on the name server, insert a variable that uses a regular expression immediately above the isInNet() argument to restrict the result just to IP addresses.

Therefore, instead of writing:

if (isInNet(host, "0.0.0.0", "255.0.0.0")||
isInNet(host, "10.0.0.0", "255.0.0.0") ||
isInNet(host, "127.0.0.0", "255.0.0.0") ||
...)
{
  return "DIRECT";
}
Write this:
reip = /^\d+\.\d+\.\d+\.\d+$/g;
if (reip.test(host))
{
 if (isInNet(host, "0.0.0.0", "255.0.0.0")||
 isInNet(host, "10.0.0.0", "255.0.0.0") ||
 isInNet(host, "127.0.0.0", "255.0.0.0") ||
    ...)
 {
  return "DIRECT";
 }      
}

Here, the variable reip = /^\d+\.\d+\.\d+\.\d+$/g; is used in the argument if(reip.test(host)) {, and the result is only used if the host is an IP address in one of the  networks specified in the nested if argument. Note that this can only be used for URLs that include IP addresses, such as http://192.0.2.3/example.

In some versions of IE, use of the preceding variable may not work. An alternative to this is to use the JavaScript shell expression match function:

if (shExpMatch(host, "/^\d+\.\d+\.\d+\.\d+$/g")) 
{
 if (isInNet(host, "10.0.0.0", "255.0.0.0") ||
 isInNet(host, "192.168.0.0", "255.255.0.0")) 
 {
  return "DIRECT";
 }      
}

Save this change, reload the PAC file in your browser, and then try browsing to an internal web server in the internal network. If you can reach the server, you have bypassed the Zscaler ZEN.

The following lines in the PAC file example exclude specific servers, such as mail.domain.com, from being redirected to a proxy. In the example, a separate if isInNet() argument lists internal host names.

    //
    //Bypass proxy for this server
    //
  if (isInNet(host mail.domain.com) 
  { 
    return “DIRECT”;
  }
//To bypass all hosts in a domain, use
dnsDomainIs(host, “host.com”)
 
You can also use the following to bypass specific internal hosts:
var bypassHosts = /(remote\.mydomain\.com|mail\.mydomain\.com)/; 
if (bypassHosts.test(host)) 
{
 
  return "DIRECT";
}

 The preceding argument includes a variable that contains two hosts: remote.mydomain.com and mail.mydomain.com. Using the JavaScript test(host) function, any host you enter here will return DIRECT, and will not require a DNS lookup. var is the JavaScript function to set a variable. bypassHosts is a JavaScript function. You must use this specific name/function. Forward slashes mark the beginning and ending boundary of the variable. Open and close parentheses in the variable match the parentheses in the argument (test(host)). The periods in the host names must be “escaped” with a back slash. The variable itself requires a semicolon to close the variable argument.

You can use the ${GATEWAY} and ${SECONDARY_GATEWAY} variables to determine the ZEN closest to the client. For example:

return "PROXY ${GATEWAY}:80; PROXY ${SECONDARY_GATEWAY}:80; DIRECT";

The Zscaler service uses its geo-location technology to find the closest ZEN with the quickest response time. These variables provide optimal user experience.

If your organization uses a subcloud, use the variables ${GATEWAY.<Organization name>.zscaler.net} and ${SECONDARY_GATEWAY.<Organization name>.zscaler.net.}. For example:

return "PROXY ${GATEWAY.safemarch.zscaler.net}:80; PROXY ${SECONDARY_GATEWAY.safemarch.zscaler.net}:80; DIRECT";

You can use the ${SRCIP} variable to determine the client's public IP address. For example:

var egressip = "${SRCIP}";
If (shExpMatch(egressip,"203.0.113.10")) {
		/* User is in the office */
		return "PROXY 10.84.0.188:80;DIRECT";
}

You can use the ${COUNTRY} variable to determine the client's country.

The ${COUNTRY} variable supports any of following countries:

  • Afghanistan
  • Aland Islands
  • Albania
  • Algeria
  • American Samoa
  • Andorra
  • Angola
  • Anguilla
  • Antarctica
  • Antigua and Barbuda
  • Argentina
  • Armenia
  • Aruba
  • Asia/Pacific Region
  • Australia
  • Austria
  • Azerbaijan
  • Bahamas
  • Bahrain
  • Bangladesh
  • Barbados
  • Belarus
  • Belgium
  • Belize
  • Benin
  • Bermuda
  • Bhutan
  • Bolivia
  • Bosnia and Herzegovina
  • Botswana
  • Bouvet Island
  • Brazil
  • British Indian Ocean Territory
  • Brunei Darussalam
  • Bulgaria
  • Burkina Faso
  • Burundi
  • Cambodia
  • Cameroon
  • Canada
  • Cape Verde
  • Cayman Islands
  • Central African Republic
  • Chad
  • Chile
  • China
  • Christmas Island
  • Cocos (Keeling) Islands
  • Colombia
  • Comoros
  • Congo (Congo-Brazzaville)
  • Cook Islands
  • Costa Rica
  • Cote D'Ivoire
  • Croatia
  • Cuba
  • Cyprus
  • Czech Republic
  • Democratic Republic of Congo (Congo-Kinshasa)
  • Denmark
  • Djibouti
  • Dominica
  • Dominican Republic
  • Ecuador
  • Egypt
  • El Salvador
  • Equatorial Guinea
  • Eritrea
  • Estonia
  • Ethiopia
  • Europe
  • Falkland Islands (Malvinas)
  • Faroe Islands
  • Federated States of Micronesia
  • Fiji
  • Finland
  • France
  • French Guiana
  • French Polynesia
  • French Southern Territories
  • Gabon
  • Gambia
  • Georgia
  • Germany
  • Ghana
  • Gibraltar
  • Greece
  • Greenland
  • Grenada
  • Guadeloupe
  • Guam
  • Guatemala
  • Guernsey
  • Guinea
  • Guinea-Bissau
  • Guyana
  • Haiti
  • Heard Island and McDonald Islands
  • Holy See (Vatican City State)
  • Honduras
  • Hong Kong
  • Hungary
  • Iceland
  • India
  • Indonesia
  • Iran
  • Iraq
  • Ireland
  • Isle of Man
  • Israel
  • Italy
  • Jamaica
  • Japan
  • Jersey
  • Jordan
  • Kazakhstan
  • Kenya
  • Kiribati
  • Kuwait
  • Kyrgyzstan
  • Laos
  • Latvia
  • Lebanon
  • Lesotho
  • Liberia
  • Libya
  • Liechtenstein
  • Lithuania
  • Luxembourg
  • Macau
  • Macedonia
  • Madagascar
  • Malawi
  • Malaysia
  • Maldives
  • Mali
  • Malta
  • Marshall Islands
  • Martinique
  • Mauritania
  • Mauritius
  • Mayotte
  • Metropolitan France
  • Mexico
  • Moldova
  • Monaco
  • Mongolia
  • Montenegro
  • Montserrat
  • Morocco
  • Mozambique
  • Myanmar
  • Namibia
  • Nauru
  • Nepal
  • Netherlands
  • Netherlands Antilles
  • New Caledonia
  • New Zealand
  • Nicaragua
  • Niger
  • Nigeria
  • Niue
  • Norfolk Island
  • North Korea
  • Northern Mariana Islands
  • Norway
  • Oman
  • Other
  • Pakistan
  • Palau
  • Palestinian Territory
  • Panama
  • Papua New Guinea
  • Paraguay
  • Peru
  • Philippines
  • Pitcairn Islands
  • Poland
  • Portugal
  • Puerto Rico
  • Qatar
  • Reunion
  • Romania
  • Russia
  • Rwanda
  • Saint Barthelemy
  • Saint Helena
  • Saint Kitts and Nevis
  • Saint Lucia
  • Saint Martin
  • Saint Pierre and Miquelon
  • Saint Vincent and the Grenadines
  • Samoa
  • San Marino
  • Sao Tome and Principe
  • Saudi Arabia
  • Senegal
  • Serbia
  • Seychelles
  • Sierra Leone
  • Singapore
  • Slovakia
  • Slovenia
  • Solomon Islands
  • Somalia
  • South Africa
  • South Georgia and the South Sandwich Islands
  • South Korea
  • Spain
  • Sri Lanka
  • Sudan
  • Suriname
  • Svalbard and Jan Mayen
  • Swaziland
  • Sweden
  • Switzerland
  • Syria
  • Taiwan
  • Tajikistan
  • Tanzania
  • Thailand
  • Timor-Leste
  • Togo
  • Tokelau
  • Tonga
  • Trinidad and Tobago
  • Tunisia
  • Turkey
  • Turkmenistan
  • Turks and Caicos Islands
  • Tuvalu
  • Uganda
  • Ukraine
  • United Arab Emirates
  • United Kingdom
  • United States
  • United States Minor Outlying Islands
  • Uruguay
  • Uzbekistan
  • Vanuatu
  • Venezuela
  • Vietnam
  • Virgin Islands (British)
  • Virgin Islands (U.S.)
  • Wallis and Futuna
  • Western Sahara
  • Yemen
  • Zambia
  • Zimbabwe

You can use the ${COUNTRY_GATEWAY} and ${COUNTRY_SECONDARY_GATEWAY} variables to determine the closest ZEN in the client's country. For example:

return "PROXY ${COUNTRY_GATEWAY}:80; PROXY ${COUNTRY_SECONDARY_GATEWAY}:80;"

If your organization uses a subcloud, use the variables ${COUNTRY_GATEWAY.<Organization name>.zscaler.net} and ${COUNTRY_SECONDARY_GATEWAY.<Organization name>.zscaler.net.}. For example:

return "PROXY ${COUNTRY_GATEWAY.safemarch.zscaler.net}:80; PROXY ${COUNTRY_SECONDARY_GATEWAY.safemarch.zscaler.net}:80; DIRECT";

This prevents clients that are near multiple ZENs or in countries that are close to one another from having their traffic forwarded to the wrong ZENs.