Zscaler PAC builder
Builds a PAC file for Zscaler Internet Access with the ${GATEWAY} variable pair, the documented service-edge ports, DNS-free bypasses for plain hostnames, private addresses and your internal domains, and an optional Forwarding Profile variant for Zscaler Client Connector.
The output follows Zscaler’s own PAC guidance (variables instead of addresses, primary plus secondary
gateway, FTP and internal traffic direct) and this site’s rules on top: no DNS lookup on the hot path,
host lower-cased, a commented default route. Host the file on the Zscaler cloud, otherwise the
${...} variables stay literal.
Generated proxy.pac
[{"default":"hosted","help":"The Forwarding Profile PAC must never send traffic to the cloud itself; the App Profile PAC does that.","id":"role","label":"Which PAC file is this?","options":[{"label":"Hosted PAC / App Profile PAC: forward to the Zscaler cloud (${GATEWAY})","value":"hosted"},{"label":"Forwarding Profile PAC for Tunnel with Local Proxy: forward to Zscaler Client Connector (${ZAPP_LOCAL_PROXY})","value":"forwarding_twlp"}],"type":"select"},{"default":"global","help":"Only used for the hosted / App Profile role.","id":"scope","label":"Gateway selection","options":[{"label":"Closest Public Service Edge (${GATEWAY} / ${SECONDARY_GATEWAY})","value":"global"},{"label":"Closest Public Service Edge in the client's country (${COUNTRY_GATEWAY} / ${COUNTRY_SECONDARY_GATEWAY})","value":"country"}],"type":"select"},{"default":"","help":"Only used for the hosted / App Profile role.","id":"suffix","label":"Variable variant","options":[{"label":"Plain (IP address of the service edge)","value":""},{"label":"_HOST: host name instead of IP (required for Kerberos and IPv6)","value":"_HOST"},{"label":"_FX: load-balanced per device fingerprint (Zscaler Client Connector clients)","value":"_FX"}],"type":"select"},{"default":"80","help":"Ports documented for Public Service Edges. Only used for the hosted / App Profile role.","id":"port","label":"Service edge port","options":[{"label":"80 (default)","value":"80"},{"label":"443","value":"443"},{"label":"9400 (when something intercepts port 80 on the way)","value":"9400"},{"label":"9443 (remote users, HTTPS inspection)","value":"9443"},{"label":"9480 (authentication exemption from known locations)","value":"9480"}],"type":"select"},{"default":["corp.example"],"help":"One per line, without a leading dot; the generator matches the domain and all its subdomains.","id":"internal_domains","item_pattern":"^[a-z0-9-]+(\\.[a-z0-9-]+)+$","label":"Internal DNS suffixes that go direct","max":50,"pattern_help":"must be a domain such as corp.example (no leading dot, no wildcard)","type":"list"},{"default":true,"help":"Uses an IP-literal test in front of isInNet, so host names never trigger a DNS lookup.","id":"bypass_private","label":"Plain hostnames and private (RFC 1918) addresses go direct","rules":["isinnet-on-hostname"],"type":"checkbox"},{"default":true,"help":"The Zscaler service does not support native FTP; the default PAC files return DIRECT for ftp URLs.","id":"ftp_direct","label":"ftp:// URLs go direct","type":"checkbox"},{"default":false,"grade_note":"Lowers the grade unless \"Accept fail-open\" is ticked in the checker settings. Zscaler Client Connector has its own fallback to gateway.\u003ccloud\u003e.net.","id":"direct_fallback","label":"Add \"; DIRECT\" after the gateways (fail open when both are unreachable)","rules":["direct-fallback-in-proxy-list"],"type":"checkbox"}]// proxy.pac for Zscaler Internet Access, generated by findproxyforurl.net
// Host this file on the Zscaler cloud so that the ${...} variables are replaced.
function FindProxyForURL(url, host) {
host = host.toLowerCase();
{{#if bypass_private}}
// plain hostnames (no dot) stay on the local network
if (isPlainHostName(host)) {
return "DIRECT";
}
// RFC 1918 and loopback addresses: literal IPs only, no DNS lookup for names
if (/^\d+\.\d+\.\d+\.\d+$/.test(host) &&
(isInNet(host, "10.0.0.0", "255.0.0.0") ||
isInNet(host, "172.16.0.0", "255.240.0.0") ||
isInNet(host, "192.168.0.0", "255.255.0.0") ||
isInNet(host, "127.0.0.0", "255.0.0.0"))) {
return "DIRECT";
}
{{/if}}
{{#if internal_domains}}
// internal domains and their subdomains go direct
if ({{#each internal_domains sep=" ||\n "}}dnsDomainIs(host, ".{{.}}"){{/each}}) {
return "DIRECT";
}
{{/if}}
{{#if ftp_direct}}
// the Zscaler service does not support native FTP
if (url.substring(0, 4) == "ftp:") {
return "DIRECT";
}
{{/if}}
{{#if role == "hosted"}}
{{#if scope == "global"}}
// default: everything else goes to the closest Public Service Edge, then to the secondary one
return "PROXY ${GATEWAY{{suffix}}}:{{port}}; PROXY ${SECONDARY_GATEWAY{{suffix}}}:{{port}}{{#if direct_fallback}}; DIRECT{{/if}}";
{{/if}}
{{#if scope == "country"}}
// default: everything else goes to the closest Public Service Edge in the client's country, then to the secondary one
return "PROXY ${COUNTRY_GATEWAY{{suffix}}}:{{port}}; PROXY ${COUNTRY_SECONDARY_GATEWAY{{suffix}}}:{{port}}{{#if direct_fallback}}; DIRECT{{/if}}";
{{/if}}
{{/if}}
{{#if role == "forwarding_twlp"}}
// default: everything else goes to the Zscaler Client Connector listener (loopback address and port
// inserted by the client); the App Profile PAC then picks the data centre
return "PROXY ${ZAPP_LOCAL_PROXY}";
{{/if}}
}
Deploying it
Upload under Infrastructure > Internet & SaaS > Traffic Forwarding > Hosted PAC Files, press
“Verify PAC File”, stage it for a small group first, then deploy. In Tunnel mode, remember that
Zscaler Client Connector intercepts port 80/443 traffic regardless of this file; domain bypasses
belong in the App Profile PAC, IP bypasses in destination exclusions or VPN gateway bypasses.
What the Zscaler pack checks
A Zscaler-hosted PAC file is a template: the PAC server replaces
${GATEWAY}, ${SECONDARY_GATEWAY}
and the other documented variables with the address of the closest Public Service Edge before the
browser sees the file. Zscaler Client Connector uses two PAC files with different jobs. The Forwarding
Profile PAC steers system, browser and application traffic to the client (or away from it), and the
App Profile PAC tells the client where in the Zscaler cloud to send what it received. In Tunnel mode
the client intercepts port 80 and 443 traffic regardless of the PAC, so a DIRECT in the PAC does not
bypass the client on its own.- Variables only expand when the PAC file is hosted on the Zscaler cloud; in a self-hosted file they stay literal.
- Public Service Edges accept browser traffic on ports 80, 443, 9400, 9443 and 9480; dedicated ports exist by subscription.
- Primary plus secondary gateway gives failover; Zscaler Client Connector additionally falls back to gateway.<cloud>.net unless disabled.
- In Tunnel mode the Forwarding Profile PAC must not send traffic to the Zscaler cloud; that is the App Profile PAC's job.
- Domain-based bypasses live in the PAC files; IP-based bypasses for Z-Tunnel 2.0 belong in destination exclusions or VPN gateway bypasses, not in the PAC.
- PAC-E019Unknown Zscaler PAC variable high
- PAC-C019Zscaler gateway on an undocumented port medium
- PAC-C020Forwarding Profile and App Profile roles mixed in one Zscaler Client Connector PAC high
- PAC-B012Single Zscaler gateway without ${SECONDARY_GATEWAY} medium
- PAC-K016Zscaler gateway named directly instead of through ${GATEWAY} low
- PAC-C023Non-HTTP URLs reach a proxy that only accepts HTTP and HTTPS low
- PAC-C026Plain hostnames or private addresses are sent to the cloud proxy medium
- PAC-B014DIRECT in the PAC does not bypass the vendor's agent info
- PAC-B015Selected vendor pack, but the PAC never routes to that vendor info
Sources
- Writing a PAC File
- Best Practices for Writing PAC Files
- Best Practices for Using PAC Files with Zscaler Client Connector
- Best Practices for Adding Bypasses for Z-Tunnel 2.0
- Configuring Forwarding Profiles for Zscaler Client Connector
- Configuring Zscaler Client Connector App Profiles
- About Application Bypass
- Load Balancing for PAC Forwarded Traffic
- About Hosted PAC Files
- Using Custom PAC Files to Forward Traffic to Internet & SaaS