The essential points from this guide -- each one is explained in detail below.
A PAC file is a JavaScript script that returns DIRECT, PROXY host:port, or SOCKS host:port for every URL a browser requests.
Browsers find a PAC file three ways: WPAD auto-discovery, a manually entered proxy pac url, or a policy pushed by MDM or Group Policy.
The FindProxyForURL() function relies on helper functions like shExpMatch(), dnsDomainIs(), and isInNet() to match domains and IP ranges.
A PAC file must be served with the application/x-ns-proxy-autoconfig MIME type, or some browsers will download it instead of running it.
Command-line tools like curl and git do not read PAC files at all; they use the http_proxy, https_proxy, and no_proxy environment variables instead.
PAC stands for Proxy Auto-Configuration. It is a plain JavaScript file, usually saved with a .pac extension, that contains one required function: FindProxyForURL(url, host). A browser calls this function before every request and uses whatever string it returns to decide where that specific request should go -- straight to the internet, through one proxy, or through a different proxy depending on the destination.
Browsers and operating systems find a PAC file in three ways. The first is WPAD (Web Proxy Auto-Discovery), where a device checks for a well-known hostname like wpad.yourcompany.com or reads DHCP option 252 from the local network, and downloads whatever PAC file that lookup points to, with no manual setup at all. The second is manual entry: you type the proxy pac url directly into your browser's or OS's network settings, under an option usually labeled "Automatic proxy configuration" or "Use setup script." The third is a policy pushed by an MDM tool or Windows Group Policy, which sets the same proxy configuration url across every managed device in one step.
All three methods point at the same thing: a single pac web address the browser fetches and re-evaluates on a schedule, not a file it downloads once and keeps forever. Because of this, updating the hosted script changes proxy behavior for every device pointed at that address, without touching a single device by hand.
You will see this same mechanism described different ways depending on where you read about it. Some call it a proxy auto configuration pac file. Others simply call it a proxy pac file. You may also see it called a pac script proxy. Or you might just know it as the auto proxy url you paste into a settings field. Some IT documentation calls that same field the proxy auto config url instead. Whatever the name, it means the same thing: one hosted JavaScript file containing FindProxyForURL(), fetched from a single address every browser on the network points at.
Every PAC file needs exactly one function, with this exact name and signature:
function FindProxyForURL(url, host) {
// url is the full address being requested
// host is just the hostname, with the path and query stripped out
return "DIRECT";
}The function must return one of a small set of strings. "DIRECT" sends the request straight to the internet, no proxy. "PROXY host:port" routes it through the named proxy. "SOCKS host:port" routes it through a SOCKS proxy. You can also return a semicolon-separated list, like "PROXY primary.example.com:7000; PROXY backup.example.com:7000; DIRECT", and the browser tries each option in order if the one before it fails to connect.
Four helper functions do most of the real work inside FindProxyForURL(). isPlainHostName(host) returns true when the hostname has no dots in it, a fast way to catch internal machine names like "printer" instead of "printer.company.com". dnsDomainIs(host, domain) checks whether a hostname ends in a given domain suffix, useful for matching every subdomain of a company's own site at once. shExpMatch(str, pattern) runs a shell-style wildcard match and is the most flexible of the four, matching patterns like "*.ads.example.com" against a hostname or full URL. isInNet(host, pattern, mask) resolves the hostname to an IP address first, then checks whether that address falls inside a given subnet, the right tool when you need to route by internal IP range instead of domain name.
A short example combining two of these:
function FindProxyForURL(url, host) {
if (isPlainHostName(host) || dnsDomainIs(host, ".internal.company.com")) {
return "DIRECT";
}
return "PROXY gw.knoxproxy.com:7000";
}This sends anything that looks like an internal hostname straight to the network, and routes everything else through a single proxy.
Two more built-in functions round out the API. dnsResolve(host) resolves a hostname to its first IP address and returns it as a plain string, without doing anything else -- use it when you need the resolved address for your own comparison logic rather than the combined lookup-and-subnet-check that isInNet() performs. myIpAddress() returns the IP address of the machine running the browser, which is useful for building a PAC file that behaves differently depending on which network the device is currently connected to, such as serving one set of rules on a corporate VPN and a different set on a home network.
A practical PAC file usually needs to do three things at once: send internal traffic direct, route specific external targets through a paid proxy, and block a short list of known ad or tracking domains before they load at all. Here is a working example that does all three:
function FindProxyForURL(url, host) {
// Internal company traffic goes direct
if (isPlainHostName(host) || dnsDomainIs(host, ".internal.company.com")) {
return "DIRECT";
}
// Block known ad domains before they load
if (shExpMatch(host, "*.doubleclick.net") || shExpMatch(host, "*.adservice.google.com")) {
return "PROXY 0.0.0.0:1";
}
// Route scraping targets through KnoxProxy residential
if (shExpMatch(host, "*.target-site.com")) {
return "PROXY gw.knoxproxy.com:7000";
}
// Everything else connects normally
return "DIRECT";
}The ad-blocking branch works by pointing matched domains at an address nothing listens on (0.0.0.0:1), so the connection fails immediately instead of loading. This is a common PAC-file trick, not a KnoxProxy-specific feature, and it only stops requests the browser itself makes through this script; it will not block ads embedded inside a proxied page's own HTML.
The scraping branch sends only *.target-site.com through gw.knoxproxy.com:7000, KnoxProxy's HTTP gateway, keeping every other domain on a direct connection. This matters for cost: since residential proxies bill per GB at $2.10, routing only the domains that actually need a rotating IP, instead of all traffic, keeps bandwidth spend proportional to what the task requires.
One limitation worth knowing before you build on this: the original PAC specification only defines DIRECT, PROXY, and SOCKS (meaning SOCKS4) as return values. Chrome and Firefox both added SOCKS5 and HTTPS as extra return types, but that support is specific to those browsers' own resolvers. If a PAC file needs to work across less common browsers or embedded evaluators, stick to PROXY and DIRECT rather than relying on the SOCKS5 extension.
A PAC file needs to live somewhere every target device can reach, at a stable address that does not change every time you edit the script. Three options cover most setups.
A local file (file:///C:/proxy.pac on Windows, or file:///Users/name/proxy.pac on Mac) works for testing on a single machine, but it does not scale: every device needs its own copy, and every edit means copying the file out again by hand.
A corporate web server is the standard choice for a team or company. Any static file host works, as long as the proxy configuration url stays the same and every device points at it once. Browsers periodically re-fetch and re-check the file rather than caching it forever, so updating the hosted script changes behavior everywhere within one fetch cycle, with no per-device changes needed.
Object storage behind a CDN, like an S3 bucket fronted by CloudFront, works the same way at higher availability, which matters for a distributed team or a company serving the pac web address to devices outside one office network.
Whichever host you choose, the server must send the file with the Content-Type header set to application/x-ns-proxy-autoconfig. Some browsers tolerate application/x-javascript as a fallback, but the ns-proxy-autoconfig type is the one the PAC standard actually expects. Serve it as text/plain or leave the header off entirely, and some browsers download the file as plain text instead of running it as a script, which looks like the proxy is simply not working.
One thing a PAC file will not cover: most command-line tools -- curl, wget, git, pip -- do not read or evaluate PAC files at all. They read the http_proxy, https_proxy, and no_proxy environment variables instead. On macOS and Linux, set these directly in your shell profile:
export http_proxy="http://gw.knoxproxy.com:7000"
export https_proxy="http://gw.knoxproxy.com:7000"
export no_proxy="localhost,127.0.0.1,.internal.company.com"This is also where the no_proxy environment variable comes in on Mac and Linux -- it lists hosts a CLI tool should reach directly, the same job a PAC file's DIRECT branch does for a browser, just written as a comma-separated list instead of JavaScript. If you need matching behavior across both browser and command-line traffic, keep the PAC file's DIRECT rules and your no_proxy list in sync by hand; the two systems do not share configuration automatically. For the full walkthrough of setting these variables persistently across logins, see Linux proxy setup.
Before rolling a new PAC file out to a whole team, paste its address directly into a browser's address bar. A correctly configured server either downloads the file or shows its raw JavaScript; if the browser instead renders a broken page or a 404, the file is not reachable at that address at all, a faster check than working backward from "the proxy isn't applying" reports from users.
Three mistakes cause most PAC file problems, and all three are quick to check once you know where to look.
Wrong MIME type is the most common. If the server does not send Content-Type: application/x-ns-proxy-autoconfig, some browsers download the file as plain text instead of running it as a script, and the proxy setting silently does nothing. Check it directly with curl -I against the hosted file's address and confirm the Content-Type header in the response.
Syntax errors in the JavaScript are the second most common cause. A single typo -- a missing brace, a misspelled function name -- breaks the whole script, and most browsers fail silently, falling back to a direct connection for every request rather than showing a visible error message. Test any new PAC file against a handful of real URLs before deploying it broadly, not just the one domain you were focused on when you wrote it.
DNS resolution failures inside isInNet() cause the third common issue. Because isInNet() resolves the hostname to an IP address every time it runs, and FindProxyForURL() runs before every single navigation, a slow or unreachable DNS server adds real, repeated latency to every request, not just the ones matching that branch.
A few tools make debugging faster than guessing. Chrome's chrome://net-internals/#proxy page shows which rule matched and which proxy Chrome picked for a specific request, alongside the regular DevTools Network tab. Standalone pac-tester command-line tools let you run FindProxyForURL() against a list of sample URLs from a terminal, without opening a browser at all, which is faster for testing a batch of rules at once. On macOS, Console.app surfaces PAC fetch and evaluation errors in the system network logs, searchable by the hosted file's hostname, which catches failures that happen before a browser's own DevTools would even see the request.
Ready to put this into practice? Browse Residential Proxies
KnoxProxy Research Team · Technical Content
Network engineers and proxy infrastructure specialists with 10+ years in anti-bot systems, web scraping, and IP routing.
90.4M+ ethically sourced residential IPs across 195 countries. Instant activation, 14-day money-back guarantee.