PAC file generator
Build a proxy.pac / wpad.dat for your own proxies: an ordered failover list, the hosts, domains and networks that go direct, a comment on every block and every entry. The output is ES5, avoids DNS lookups and scores A+ in the checker.
Enter your proxies in failover order and the destinations that must not use them. Every line may
carry a comment after
#; it ends up next to the entry in the generated file, so the next person
knows why it is there. The file is built in your browser; nothing is sent anywhere until you open
it in the checker.Generated proxy.pac
[{"default":["proxy1.corp.example:8080 # primary, data centre 1","proxy2.corp.example:8080 # secondary, data centre 2"],"entry":"proxy","group":"Proxies","help":"One per line: [PROXY|HTTPS|SOCKS5] host:port, then an optional # comment. PROXY is the default type. Clients try the entries top to bottom and move on when a proxy does not answer.","id":"proxies","label":"Proxies, in failover order","max":10,"min":1,"placeholder":"proxy1.corp.example:8080 # primary","type":"entries"},{"default":"Default route: everything not matched above goes through the corporate proxies","group":"Proxies","help":"Written above the final return statement, the line that decides every request nothing else matched.","id":"proxies_note","label":"Block comment","max":5,"type":"comment"},{"default":false,"grade_note":"Lowers the grade unless \"Accept fail-open\" is ticked in the checker settings. A proxy.pac is for routing, not for security: with this option, clients bypass the proxy whenever it is down, so the firewall must decide whether that is allowed.","group":"Proxies","id":"direct_fallback","label":"Fall back to DIRECT when no proxy answers (fail-open)","rules":["direct-fallback-in-proxy-list"],"type":"checkbox"},{"default":[".corp.example # internal DNS zone and all its hosts","wiki.partner.example # partner wiki, reached over the site-to-site VPN"],"entry":"host","group":"Direct hosts and domains","help":"One per line. A name (wiki.partner.example) matches exactly that host; a name with a leading dot (.corp.example) matches the domain itself and every subdomain. No wildcards, no IP addresses (those go in the networks list).","id":"direct_hosts","label":"Hosts and domains that go direct","max":200,"placeholder":".corp.example # internal zone","type":"entries"},{"default":"Internal hosts and domains are reached without the proxy","group":"Direct hosts and domains","id":"direct_hosts_note","label":"Block comment","max":5,"type":"comment"},{"default":["198.51.100.0/24 # DMZ servers"],"entry":"network","group":"Direct networks","help":"IPv4 networks in CIDR notation, one per line (a bare address means /32). The network address must be aligned (10.20.0.0/16, not 10.20.1.0/16). They apply to IP-literal URLs only: the file never resolves a host name to compare it with a network, because that DNS lookup would block every request.","id":"direct_networks","label":"Networks that go direct","max":200,"placeholder":"10.20.0.0/16 # branch office","private_option":"private_literals","rules":["isinnet-on-hostname","dns-function-on-hot-path"],"type":"entries"},{"default":"Internal networks, matched for IP-address URLs only (no DNS lookup)","group":"Direct networks","id":"direct_networks_note","label":"Block comment","max":5,"type":"comment"},{"default":true,"group":"Options","help":"Host names are case-insensitive, string comparisons are not.","id":"lowercase","label":"Lower-case the host before comparing","rules":["host-not-lowercased"],"type":"checkbox"},{"default":true,"group":"Options","help":"Cheap and usually right inside a company network; a mistyped public name then fails locally instead of at the proxy.","id":"plain_hostnames","label":"Plain host names (no dot, such as http://intranet/) go direct","rules":["plain-hostname-blanket-direct"],"type":"checkbox"},{"default":true,"group":"Options","help":"10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8 and 169.254.0.0/16, for IP-literal URLs only.","id":"private_literals","label":"Private, loopback and link-local IP addresses go direct","type":"checkbox"},{"default":false,"group":"Bypass presets","help":"Identity-provider traffic that must not depend on proxy authentication (avoids sign-in loops).","id":"idp_entra","label":"Microsoft Entra ID sign-in (login.microsoftonline.com, aadcdn.msauth.net, aadcdn.msftauth.net)","type":"checkbox"},{"default":false,"group":"Bypass presets","id":"idp_okta","label":"Okta (okta.com, oktacdn.com and their subdomains)","type":"checkbox"},{"default":false,"group":"Bypass presets","id":"idp_google","label":"Google sign-in (accounts.google.com)","type":"checkbox"},{"default":[],"group":"File header","help":"Optional lines at the top of the file (owner, change reference). Plain ASCII, no personal data needed.","id":"file_comment","label":"File comment","max":10,"placeholder":"Owner: network team, change ticket NET-1234","type":"comment"}]// proxy.pac generated by the findproxyforurl.net PAC generator {{generator_version}} on {{generated_on}}
// Test changes at https://findproxyforurl.net/check/ before deploying them.
{{#each file_comment}}
// {{.|comment}}
{{/each}}
function FindProxyForURL(url, host) {
{{#if lowercase}}
// host names are case-insensitive, string comparisons are not
host = host.toLowerCase();
{{/if}}
{{#if plain_hostnames}}
// plain host names (no dot) stay on the local network
if (isPlainHostName(host)) {
return "DIRECT";
}
{{/if}}
{{#if direct_hosts}}
{{#each direct_hosts_note}}
// {{.|comment}}
{{/each}}
{{#if !direct_hosts_note}}
// hosts and domains that go direct
{{/if}}
if ({{#each direct_hosts sep="\n "}}{{#if .exact}}host == "{{.name}}"{{/if}}{{#if .domain}}{{#if .apex}}host == "{{.name}}" || {{/if}}dnsDomainIs(host, ".{{.name}}"){{/if}}{{#if !@last}} ||{{/if}}{{#if @last}}) {{{/if}}{{#if .comment}} // {{.comment|comment}}{{/if}}{{/each}}
return "DIRECT";
}
{{/if}}
{{#if idp_entra}}
// identity provider (Microsoft Entra ID) sign-in goes direct
if (host == "login.microsoftonline.com" || host == "aadcdn.msauth.net" || host == "aadcdn.msftauth.net") {
return "DIRECT";
}
{{/if}}
{{#if idp_okta}}
// identity provider (Okta) goes direct
if (host == "okta.com" || dnsDomainIs(host, ".okta.com") ||
host == "oktacdn.com" || dnsDomainIs(host, ".oktacdn.com")) {
return "DIRECT";
}
{{/if}}
{{#if idp_google}}
// identity provider (Google sign-in) goes direct
if (host == "accounts.google.com") {
return "DIRECT";
}
{{/if}}
{{#if private_literals || direct_networks}}
// IP-address URLs only: isInNet() on a host name would trigger a DNS lookup
if (/^\d+\.\d+\.\d+\.\d+$/.test(host)) {
{{#if private_literals}}
// private, loopback and link-local addresses (RFC 1918, RFC 1122, RFC 3927)
if (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") ||
isInNet(host, "169.254.0.0", "255.255.0.0")) {
return "DIRECT";
}
{{#if direct_networks}}
{{/if}}
{{/if}}
{{#if direct_networks}}
{{#each direct_networks_note}}
// {{.|comment}}
{{/each}}
{{#if !direct_networks_note}}
// networks that go direct
{{/if}}
if ({{#each direct_networks sep="\n "}}isInNet(host, "{{.net}}", "{{.mask}}"){{#if !@last}} ||{{/if}}{{#if @last}}) {{{/if}} // {{.cidr}}{{#if .comment}} {{.comment|comment}}{{/if}}{{/each}}
return "DIRECT";
}
{{/if}}
}
{{/if}}
{{#each proxies_note}}
// {{.|comment}}
{{/each}}
{{#if !proxies_note}}
// default route: everything not matched above goes through the proxies
{{/if}}
// failover order:
{{#each proxies}}
// {{@number}}. {{.pac}}{{#if .comment}} {{.comment|comment}}{{/if}}
{{/each}}
{{#if direct_fallback}}
// then DIRECT: fail-open, clients bypass the proxies when none answers
{{/if}}
return "{{#each proxies sep="; "}}{{.pac}}{{/each}}{{#if direct_fallback}}; DIRECT{{/if}}";
}
Deploying it
Serve the file over HTTPS (or at least from a server you control) with the content type
application/x-ns-proxy-autoconfig and a short cache lifetime, for example
Cache-Control: max-age=3600. Name it wpad.dat only if clients discover it through WPAD; prefer
a configured PAC URL (group policy, MDM) over DNS-based WPAD discovery. Test it in the checker,
roll it out to a pilot group first, and keep the previous version at hand for a quick rollback.What the generated file does
- Lower-cases the host, so that
WIKI.Corp.Exampleandwiki.corp.exampletake the same route. - Sends plain host names, your hosts and domains, and (for IP-address URLs only) private and listed networks direct, cheapest test first. Nothing in the file resolves a name, so no request waits for DNS.
- Sends everything else to your proxies in the order you listed them, with a comment on the default route: the line that decides every request nothing else matched.
There is no DIRECT at the end of the proxy list unless you ask for it. A proxy.pac is for routing,
not for security, but a silent fail-open should be a decision, not a default.
Using a security service edge?
The SSE builders produce files for Zscaler, Netskope, Skyhigh Security, Cloudflare Gateway and Prisma Access that follow each vendor’s documentation; the checker rates them with the vendor pack.