The PAC file
reference you need.
Every function, return value, best practices, security guide, WPAD, testing tools, and a live in-browser tester — all in one place.
About this site
Why this page exists.
findproxyforurl.com was for years the go-to reference for anyone writing or debugging PAC files — a clean, no-frills page that documented every helper function with working examples. When it went offline, it left a real gap: most alternative documentation is scattered across MDN, MSDN knowledge-base articles, and decade-old forum threads.
This site was built to fill that gap. It covers everything findproxyforurl.com did — and more: best practices, security considerations, WPAD auto-discovery, performance guidance, testing workflows, and an in-browser PAC tester. It's open-source and community-maintained.
Found an error or want to contribute? Open a pull request on GitHub.
What is a PAC file?
A plain-text JavaScript file that tells browsers which proxy to use, per request.
A Proxy Auto-Configuration (PAC) file contains exactly one JavaScript function:
FindProxyForURL(url, host). Browsers call this function for every outbound
request. Your function inspects the URL and hostname and returns a string telling the browser what to do.
PAC files were introduced by Netscape in 1996 and remain broadly supported across all major browsers and
operating systems today.
| Parameter | Type | Description |
|---|---|---|
| url | string | The full URL being requested. For HTTPS, the path and query are stripped — only scheme + host + port are available. This is a known browser security restriction. |
| host | string | The hostname extracted from the URL. Port numbers are removed. Identical to the host portion of the URL. |
window, fetch, XMLHttpRequest, localStorage,
setTimeout) are unavailable. Only the built-in PAC helper functions listed on this page work.
Quick start
A solid production template to build from.
function FindProxyForURL(url, host) { // 1. Bypass proxy for plain (unqualified) hostnames if (isPlainHostName(host)) { return "DIRECT"; } // 2. Bypass for internal corporate domains if ( dnsDomainIs(host, ".corp.internal") || dnsDomainIs(host, ".corp.com")) { return "DIRECT"; } // 3. Bypass for RFC1918 private ranges — resolve once, reuse var ip = dnsResolve(host); if (ip && ( isInNet(ip, "10.0.0.0", "255.0.0.0") || isInNet(ip, "172.16.0.0", "255.240.0.0") || isInNet(ip, "192.168.0.0", "255.255.0.0") || isInNet(ip, "127.0.0.0", "255.0.0.0"))) { return "DIRECT"; } // 4. Everything else: primary proxy → fallback proxy → direct return "PROXY proxy1.corp.com:8080; PROXY proxy2.corp.com:8080; DIRECT"; }
Return values
Strings returned by FindProxyForURL control how each request is routed.
Proxy chaining & fallback
Multiple directives in a return string create an ordered fallback chain.
When you return multiple directives separated by semicolons, the browser tries them strictly left to right, in the order written. It only moves to the next directive if the current one is unreachable or times out — it does not load-balance across them automatically.
return "PROXY primary.corp.com:8080; PROXY backup.corp.com:8080; DIRECT"; // ↑ tried first ↑ tried if primary fails ↑ last resort
- The browser connects to primary.corp.com:8080 first. If it responds, all traffic goes through it.
- If
primaryis unreachable or times out, the browser automatically retries via backup.corp.com:8080. DIRECTat the end means if both proxies fail, the browser will attempt a direct connection as a last resort. OmitDIRECTif you never want requests to bypass the proxy.- Proxy failover adds latency — the browser must wait for a connection timeout before trying the next entry. Keep fallback chains short and use reliable primary proxies.
Function reference
All built-in PAC helper functions with full signatures, parameters, and examples.
Returns true if host contains no dots — meaning it is an unqualified short hostname like localhost or intranet rather than a fully-qualified domain name. The classic, zero-cost test for "is this an internal host?"
| Param | Type | Description |
|---|---|---|
| host | string | The hostname portion of the URL (no port). |
isPlainHostName("www") // true — no dots isPlainHostName("www.google.com") // false — has dots if (isPlainHostName(host)) { return "DIRECT"; // bypass proxy for intranet names }
Returns true if host belongs to the given domain via suffix match. The domain must start with a leading dot. Note: this does not match the bare apex domain — "mozilla.org" does not match ".mozilla.org". No DNS lookup is performed.
| Param | Type | Description |
|---|---|---|
| host | string | Hostname to test. |
| domain | string | Domain to match — must start with a dot, e.g. ".example.com". |
dnsDomainIs("www.mozilla.org", ".mozilla.org") // true dnsDomainIs("mozilla.org", ".mozilla.org") // false — no subdomain! dnsDomainIs("www.google.com", ".mozilla.org") // false if ( dnsDomainIs(host, ".corp.internal") || dnsDomainIs(host, ".corp.com")) { return "DIRECT"; }
Returns true if host matches hostdom exactly, or if host is the short (unqualified) version of hostdom. This lets you match both www and www.example.com in a single call.
localHostOrDomainIs("www.mozilla.org", "www.mozilla.org") // true localHostOrDomainIs("www", "www.mozilla.org") // true (short name) localHostOrDomainIs("www.google.com", "www.mozilla.org") // false
Performs a live DNS lookup for host. Returns true if the hostname resolves, false if not. Triggers a real DNS query — adds latency to every PAC evaluation. Prefer dnsDomainIs() or shExpMatch() where possible.
// Go direct if hostname resolves in internal DNS if (isResolvable(host)) { return "DIRECT"; } return "PROXY proxy.corp.com:8080";
Returns true if the IP address of host falls within the subnet defined by pattern and mask. If host is a hostname (not already an IP), a DNS lookup is triggered internally. Always pass a pre-resolved IP via dnsResolve(host) to avoid double lookups and silent failures.
pattern — never hostnames. Passing a hostname to pattern can fail silently in some implementations.| Param | Type | Description |
|---|---|---|
| host | string | IP address or hostname to test. Prefer passing a pre-resolved IP. |
| pattern | string | Network address, e.g. "10.0.0.0". |
| mask | string | Subnet mask, e.g. "255.255.0.0". |
var ip = dnsResolve(host); // resolve once if (ip && ( isInNet(ip, "10.0.0.0", "255.0.0.0") || isInNet(ip, "172.16.0.0", "255.240.0.0") || isInNet(ip, "192.168.0.0", "255.255.0.0"))) { return "DIRECT"; }
Resolves a hostname to its IP address. Returns a dotted-decimal string (e.g. "93.184.216.34"), or an empty string "" on failure. Always store the result in a variable and reuse it — calling dnsResolve() multiple times for the same host triggers multiple DNS queries.
var ip = dnsResolve(host); // one DNS call — store and reuse if (!ip) { return "DIRECT"; } // guard against resolution failure if (isInNet(ip, "10.0.0.0", "255.0.0.0")) { return "DIRECT"; }
Returns the IP address of the client machine as a dotted-decimal string. Useful for routing decisions based on the client's subnet — for example, picking a regionally appropriate proxy. In some environments this returns "127.0.0.1" — see Troubleshooting.
// Select a regional proxy based on the client's subnet var myip = myIpAddress(); if (isInNet(myip, "10.1.0.0", "255.255.0.0")) { return "PROXY eu-proxy.corp.com:8080"; } if (isInNet(myip, "10.2.0.0", "255.255.0.0")) { return "PROXY us-proxy.corp.com:8080"; } return "DIRECT";
Returns the number of dots in the hostname. "intranet" → 0, "mozilla.org" → 1, "www.mozilla.org" → 2. A DNS-free alternative to isPlainHostName() that also works for multi-level internal domains.
dnsDomainLevels("www") // 0 dnsDomainLevels("mozilla.org") // 1 dnsDomainLevels("www.mozilla.org") // 2 if (dnsDomainLevels(host) < 2) { return "DIRECT"; // treat short names as internal }
Matches str against a shell glob pattern. * matches any sequence of characters, ? matches any single character. This is not a regular expression. Can be applied to both host and the full url — no DNS lookup is triggered.
shExpMatch("www.google.com", "*.google.*") // true shExpMatch("www.bing.com", "*.google.*") // false shExpMatch("ftp://example.com/pub", "ftp:*") // true // Match on full URL (including path — works for HTTP, not HTTPS) if (shExpMatch(url, "https://api.example.com/*")) { return "PROXY fast-proxy.corp.com:8080"; }
Returns true if the current day falls within the specified range. Valid values: "SUN" "MON" "TUE" "WED" "THU" "FRI" "SAT". Ranges wrap around — weekdayRange("FRI","MON") matches Fri, Sat, Sun, Mon. Append "GMT" to use UTC.
if ( weekdayRange("MON", "FRI") && timeRange(8, 18)) { return "PROXY proxy.corp.com:8080"; // business hours } return "DIRECT"; // evenings and weekends go direct
Returns true if the current date falls within the given range. Month names: "JAN"–"DEC". Day numbers: 1–31. Years: four-digit. Arguments can be mixed and matched — you can specify just months, just years, or full date ranges. Append "GMT" for UTC.
dateRange("JAN", "MAR") // true in Q1 dateRange(1, "JAN", 2025, 31, "DEC", 2025) // true all of 2025 dateRange(1, 15) // true on days 1–15 of any month
Returns true if the current time falls within the given range. Hours use 24-hour format (0–23). Specify just hours, hours+minutes, or full hours+minutes+seconds. Append "GMT" for UTC evaluation.
timeRange(9, 17) // true between 09:00 and 17:00 timeRange(9, 30, 17, 0) // true between 09:30 and 17:00 timeRange(12) // true during 12:00:00–12:59:59
Outputs a debug message during PAC evaluation. In Firefox it appears in the browser console. Not available in all environments — in some browsers, calling alert() silently breaks PAC evaluation entirely.
alert() calls in a production PAC file. Remove all debug calls before deploying.alert("PAC: evaluating host = " + host); alert(isInNet(dnsResolve(host), "10.0.0.0", "255.0.0.0"));
Compute-intensive functions
Some PAC functions trigger synchronous DNS lookups that block the browser. Know which ones to avoid.
PAC evaluation is synchronous and blocking — the browser freezes the request
until FindProxyForURL returns. Functions that perform DNS resolution amplify this
effect: every DNS round-trip adds tens to hundreds of milliseconds to every request.
dnsDomainIs() or shExpMatch() wherever possible.The golden rule:
function FindProxyForURL(url, host) { // ✓ FAST: domain checks first — zero DNS cost if ( isPlainHostName(host) || dnsDomainIs(host, ".corp.com") || shExpMatch(host, "*.local")) { return "DIRECT"; } // ✓ ONE DNS call — stored and reused below var ip = dnsResolve(host); if (!ip) { return "DIRECT"; } // ✓ guard failure case // ✓ FAST: isInNet receives an IP — no extra DNS if ( isInNet(ip, "10.0.0.0", "255.0.0.0") || isInNet(ip, "192.168.0.0", "255.255.0.0")) { return "DIRECT"; } // ✗ WRONG — triggers a second DNS lookup for the same host: // if (isInNet(dnsResolve(host), "10.0.0.0", "255.0.0.0")) { ... } return "PROXY proxy.corp.com:8080"; }
Special use cases
Advanced patterns for load balancing, geo-routing, and path-based routing.
Load balancing by client IP last octet
Deterministically split traffic between two proxies based on the client machine's IP address. Even-numbered last octet → proxy A; odd → proxy B. Each client is always sticky to the same proxy.
function FindProxyForURL(url, host) { if (isPlainHostName(host)) { return "DIRECT"; } var myip = myIpAddress(); // e.g. "192.168.1.24" var parts = myip.split("."); var lastOctet = parseInt(parts[3], 10); if ((lastOctet % 2) === 0) { return "PROXY proxy-a.corp.com:8080; PROXY proxy-b.corp.com:8080"; } else { return "PROXY proxy-b.corp.com:8080; PROXY proxy-a.corp.com:8080"; } }
Load balancing by hostname hash
Distribute requests across N proxies based on a hash of the target hostname. The same target host always routes to the same proxy — useful for connection reuse and cache warming.
function FindProxyForURL(url, host) { if (isPlainHostName(host)) { return "DIRECT"; } var proxies = [ "PROXY proxy-0.corp.com:8080", "PROXY proxy-1.corp.com:8080", "PROXY proxy-2.corp.com:8080" ]; // Simple hash: sum of char codes mod N var hash = 0; for (var i = 0; i < host.length; i++) { hash += host.charCodeAt(i); } return proxies[hash % proxies.length] + "; PROXY fallback.corp.com:8080"; }
Geo-routing by client subnet
Route clients on different office subnets through their nearest regional proxy to minimise latency.
function FindProxyForURL(url, host) { if (isPlainHostName(host)) { return "DIRECT"; } var myip = myIpAddress(); if (isInNet(myip, "10.10.0.0", "255.255.0.0")) { return "PROXY proxy-eu.corp.com:8080"; // London office } if (isInNet(myip, "10.20.0.0", "255.255.0.0")) { return "PROXY proxy-us.corp.com:8080"; // New York office } if (isInNet(myip, "10.30.0.0", "255.255.0.0")) { return "PROXY proxy-apac.corp.com:8080"; // Singapore office } return "PROXY proxy-default.corp.com:8080"; }
URL path-based routing
Route different URL patterns to different proxies. Note: path matching only works for HTTP — HTTPS strips the path before PAC evaluation.
function FindProxyForURL(url, host) { // Route API traffic to a dedicated high-bandwidth proxy if ( shExpMatch(url, "http://api.example.com/*") || dnsDomainIs(host, ".api.example.com")) { return "PROXY api-proxy.corp.com:8080"; } // Route streaming to a bypass proxy if ( dnsDomainIs(host, ".netflix.com") || dnsDomainIs(host, ".spotify.com") || dnsDomainIs(host, ".youtube.com")) { return "DIRECT"; // bypass proxy for high-bandwidth streaming } return "PROXY default.corp.com:8080"; }
Best practices
Patterns that make PAC files reliable, fast, and maintainable.
- DNS-free checks go first. Order your conditions so that
isPlainHostName(),dnsDomainIs(), andshExpMatch()are evaluated before anything that triggers DNS. The majority of internal traffic matches these checks and exits early — saving DNS round-trips for the rest. - Resolve DNS once. Assign
dnsResolve(host)to a variable at the top of the function and reuse it for everyisInNet()call. CallingdnsResolve()orisResolvable()multiple times for the same host multiplies your DNS cost. - Guard DNS failures.
dnsResolve()returns an empty string""on failure, notnull. Always checkif (ip && ...)before passing toisInNet()— an empty string will produce incorrect matches. - Logical operators at the start of lines. In multi-line conditions, place
||and&&at the beginning of each continuation line, not the end. This way every line is self-contained — you can safely copy/paste, delete, or comment out any line without worrying about whether the previous line now has a dangling operator. - Always include a catch-all return. The last line of your function must unconditionally return something. If execution falls through without returning, the browser's behaviour is undefined — it may throw an error, go direct, or refuse the request entirely.
- Serve with the correct MIME type. The PAC file must be served as
application/x-ns-proxy-autoconfig. Some clients also acceptapplication/x-javascript-configortext/javascript, but the former is the most widely supported. An incorrect MIME type will cause clients to silently ignore the file. - Keep fallback chains short. Each entry in a semicolon-separated return chain adds a full connection timeout before the browser moves to the next one. Limit yourself to two proxies plus
DIRECTat most. If you need more proxies, use load balancing logic in the PAC file itself. - Version your PAC files. Add a comment at the top with a version number and date. PAC files are often cached aggressively — a version comment makes it easy to verify which version a client actually loaded during debugging.
- Remove all
alert()calls before deploying. Debug alerts break PAC evaluation in some browsers and can expose internal routing logic to end users. Usepacparseror browser DevTools for testing instead. - Use IP addresses in subnet patterns. In
isInNet(host, pattern, mask),patternmust always be a dotted-decimal IP — never a hostname. Hostnames inpatterncan fail silently across browsers.
Security
PAC files are a high-value attack target. Understand the risks before deploying.
- Serve PAC files over HTTPS. The original PAC spec used plain HTTP. A man-in-the-middle attacker on the same network can intercept an HTTP PAC request and inject a malicious PAC file, silently redirecting all your traffic. Always serve PAC files from an HTTPS URL.
- WPAD auto-discovery is inherently risky. WPAD uses DHCP and DNS to locate your PAC file automatically. A rogue DHCP server or DNS poisoning attack can serve any machine on the network a malicious PAC URL. Disable WPAD auto-discovery in environments where you don't control the network, and configure the PAC URL explicitly.
- Never embed credentials in PAC files. PAC files are served as plain text and cached in the browser. Never include proxy usernames, passwords, API keys, or any other secrets in your PAC file.
- Restrict PAC file access. Your PAC file server should only be accessible from internal networks. A publicly accessible PAC file reveals your internal network topology, proxy addresses, and IP ranges to anyone who fetches it.
- HTTPS path stripping is a known limitation. For HTTPS URLs, the browser only passes the scheme, host, and port to
FindProxyForURL— the path and query are stripped before PAC evaluation. Do not rely on URL path matching for security-critical routing decisions involving HTTPS traffic. - PAC files can be weaponised via DNS rebinding. A malicious PAC file could call
dnsResolve()on attacker-controlled domains to map your internal IP space, or useisInNet()to probe your network topology. Keep PAC evaluation sandboxed and never serve third-party PAC files.
WPAD auto-discovery
How browsers automatically find your PAC file without manual configuration.
Web Proxy Auto-Discovery (WPAD) lets browsers find the PAC file URL automatically using DHCP or DNS,
eliminating the need to configure the PAC URL on every client machine. The convention is to name the
file wpad.dat (instead of proxy.pac) when using WPAD.
wpad. to the domain: e.g. http://wpad.corp.com/wpad.dat. The first hostname that resolves and serves a valid PAC file wins.wpad.dat at the WPAD URL with MIME type application/x-ns-proxy-autoconfig. Ensure the hostname wpad.yourdomain.com resolves in internal DNS.Testing & debugging
How to validate your PAC file before deploying it to production.
pacparser — command-line PAC tester
The most reliable way to test PAC files outside a browser. Runs the full PAC evaluation engine, not a simulation.
# Install (macOS) brew install pacparser # Install (Ubuntu / Debian) sudo apt install pacparser # Test a PAC file against a URL pactester -p proxy.pac -u https://www.google.com -h www.google.com # Expected output: # PROXY proxy.corp.com:8080 # Test multiple URLs from a file pactester -p proxy.pac -f urls.txt
Firefox browser debugging
Firefox exposes PAC file evaluation and alert() output in the browser console.
# 1. Open about:config → set network.proxy.autoconfig_url to your PAC file URL # 2. Set network.proxy.type = 2 (use PAC file) # 3. Open DevTools → Browser Console (not page console) # 4. alert() calls in your PAC file appear as "PAC-alert: ..." messages # 5. Navigate to a URL and watch the console for evaluation output
function FindProxyForURL(url, host) { var result = _findProxy(url, host); alert("PAC: " + host + " → " + result); return result; } function _findProxy(url, host) { if (isPlainHostName(host)) { return "DIRECT"; } return "PROXY proxy.corp.com:8080"; }
Chrome / macOS netlog
Chrome provides a network log viewer that shows PAC evaluation results per request.
# Open chrome://net-export/ → Start Logging → browse → Stop # Open https://netlog-viewer.appspot.com/ → load the exported JSON # Filter events by "PAC_RESOLVE" to see per-request PAC results # macOS: test PAC from the command line using scutil scutil --proxy # shows current proxy configuration
Also try the Live Tester on this page for quick in-browser validation. It simulates all PAC helper functions and evaluates your PAC file client-side — no data is sent anywhere.
Troubleshooting
Common PAC file bugs and how to fix them.
| Symptom | Cause & Fix |
|---|---|
| myIpAddress() returns 127.0.0.1 | The browser can't determine the machine's external interface IP. This often happens on machines with no active network adapters or unusual routing tables. Some browsers (especially Chrome) always return 127.0.0.1 in certain OS configurations. Workaround: use subnet detection via dnsResolve() on a known internal host instead, or avoid myIpAddress()-based logic in WPAD deployments. |
| HTTPS URL path not available | Chrome, Firefox, and all modern browsers strip the path and query from HTTPS URLs before calling FindProxyForURL. You receive only https://hostname:443. shExpMatch(url, "*/some/path*") will never match for HTTPS. Path-based routing only works for plain HTTP. |
| PAC file changes not picked up | Browsers cache PAC files aggressively — often for the entire browser session. To force a reload: restart the browser completely, or use a short Cache-Control: max-age=300 header on your PAC file server. During development, use pacparser to test outside the browser. |
| isInNet() always returns false | You're passing a hostname to isInNet() that failed DNS resolution — dnsResolve() returned "", and isInNet("", ...) returns false. Always guard: var ip = dnsResolve(host); if (ip && isInNet(ip, ...)). |
| PAC file ignored entirely | Wrong MIME type. The server must respond with Content-Type: application/x-ns-proxy-autoconfig. A text/plain or missing content-type causes many browsers to silently reject the file. |
| alert() breaks PAC in IE/Edge | Internet Explorer and legacy Edge treat alert() as an invalid API in the PAC sandbox — calling it causes the entire PAC function to fail silently and the browser falls through to direct connection. Remove all alert() calls before deploying. |
| dnsDomainIs() misses apex domain | dnsDomainIs("example.com", ".example.com") returns false — the bare apex doesn't match the dotted suffix. Combine with an exact check: host === "example.com" || dnsDomainIs(host, ".example.com"). |
| PAC works in Firefox but not Chrome | Chrome caches PAC results per-hostname for the duration of a network profile change, ignoring path-based differences. Chrome also resolves DNS from the host OS rather than the browser's DNS stack in some configurations. Test with pacparser to isolate PAC logic from browser-specific quirks. |
Common examples
Real-world PAC files for the most common deployment scenarios.
Corporate: bypass internal, proxy external
The most common enterprise pattern. Internal hosts and RFC1918 ranges go direct; everything else uses a proxy with failover.
function FindProxyForURL(url, host) { // Fast checks first — no DNS cost if (isPlainHostName(host)) { return "DIRECT"; } if ( dnsDomainIs(host, ".corp.internal") || dnsDomainIs(host, ".corp.com")) { return "DIRECT"; } // One DNS call — reused for all subnet checks var ip = dnsResolve(host); if (ip && ( isInNet(ip, "10.0.0.0", "255.0.0.0") || isInNet(ip, "172.16.0.0", "255.240.0.0") || isInNet(ip, "192.168.0.0", "255.255.0.0") || isInNet(ip, "127.0.0.0", "255.0.0.0"))) { return "DIRECT"; } return "PROXY proxy1.corp.com:8080; PROXY proxy2.corp.com:8080; DIRECT"; }
Split-tunnel by protocol
Route HTTP through one proxy, HTTPS through another, FTP directly.
function FindProxyForURL(url, host) { if (shExpMatch(url, "http:*")) { return "PROXY http-proxy.corp.com:8080"; } if (shExpMatch(url, "https:*")) { return "PROXY ssl-proxy.corp.com:8443"; } if (shExpMatch(url, "ftp:*")) { return "DIRECT"; } return "DIRECT"; }
Time-based routing
Use the corporate proxy during business hours; go direct outside those hours.
function FindProxyForURL(url, host) { if (isPlainHostName(host)) { return "DIRECT"; } if ( weekdayRange("MON", "FRI") && timeRange(8, 18)) { return "PROXY proxy.corp.com:8080"; } return "DIRECT"; }
SOCKS5 with HTTP fallback
Try a SOCKS5 proxy first, then fall back to an HTTP proxy, then direct.
function FindProxyForURL(url, host) { if (isPlainHostName(host)) { return "DIRECT"; } return "SOCKS5 socks.corp.com:1080; PROXY proxy.corp.com:8080; DIRECT"; }
Live tester
Paste your PAC file and evaluate it against any URL — runs entirely in your browser. Ctrl+Enter to run.
In-browser PAC evaluator
All helper functions are simulated in JavaScript. No data is sent anywhere. DNS calls return simulated results.