Proxy Auto-Configuration

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.

function FindProxyForURL(url, host) { // ← you write this

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.

ParameterTypeDescription
urlstring 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.
hoststring The hostname extracted from the URL. Port numbers are removed. Identical to the host portion of the URL.
PAC files run in a sandboxed JavaScript environment. Standard browser APIs (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.

proxy.pac — production template
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.

DIRECT
Connect directly to the destination server. No proxy is used.
"DIRECT"
PROXY host:port
Route through an HTTP/HTTPS proxy at the given host and port.
"PROXY proxy.corp.com:8080"
SOCKS host:port
Route through a SOCKS proxy (v4 or v5). Broadly supported shorthand.
"SOCKS socks.corp.com:1080"
SOCKS4 / SOCKS5
Explicitly select SOCKS version 4 or 5 when the version matters.
"SOCKS5 socks5.corp.com:1080"

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.

fallback chain — evaluated left → right
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 primary is unreachable or times out, the browser automatically retries via backup.corp.com:8080.
  • DIRECT at the end means if both proxies fail, the browser will attempt a direct connection as a last resort. Omit DIRECT if 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.

isPlainHostName(host)
→ boolean

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?"

ParamTypeDescription
hoststringThe hostname portion of the URL (no port).
example
isPlainHostName("www")            // true  — no dots
isPlainHostName("www.google.com") // false — has dots

if (isPlainHostName(host)) {
  return "DIRECT"; // bypass proxy for intranet names
}
dnsDomainIs(host, domain)
→ boolean

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.

ParamTypeDescription
hoststringHostname to test.
domainstringDomain to match — must start with a dot, e.g. ".example.com".
example
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";
}
localHostOrDomainIs(host, hostdom)
→ boolean

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.

example
localHostOrDomainIs("www.mozilla.org", "www.mozilla.org") // true
localHostOrDomainIs("www",             "www.mozilla.org") // true  (short name)
localHostOrDomainIs("www.google.com",  "www.mozilla.org") // false
isResolvable(host)
→ boolean

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.

DNS queries block the browser during PAC evaluation. See the Compute-intensive functions section for strategies to minimise DNS calls.
example
// Go direct if hostname resolves in internal DNS
if (isResolvable(host)) {
  return "DIRECT";
}
return "PROXY proxy.corp.com:8080";
isInNet(host, pattern, mask)
→ boolean

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.

Always use dotted-decimal IP addresses in pattern — never hostnames. Passing a hostname to pattern can fail silently in some implementations.
ParamTypeDescription
hoststringIP address or hostname to test. Prefer passing a pre-resolved IP.
patternstringNetwork address, e.g. "10.0.0.0".
maskstringSubnet mask, e.g. "255.255.0.0".
example — resolve once, test multiple subnets
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";
}
dnsResolve(host)
→ string

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.

example
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";
}
myIpAddress()
→ string

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.

example
// 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";
dnsDomainLevels(host)
→ number

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.

example
dnsDomainLevels("www")             // 0
dnsDomainLevels("mozilla.org")     // 1
dnsDomainLevels("www.mozilla.org") // 2

if (dnsDomainLevels(host) < 2) {
  return "DIRECT"; // treat short names as internal
}
shExpMatch(str, shexp)
→ boolean

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.

example
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";
}
weekdayRange(wd1, wd2[opt], gmt[opt])
→ boolean

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.

example
if ( weekdayRange("MON", "FRI")
  && timeRange(8, 18)) {
  return "PROXY proxy.corp.com:8080"; // business hours
}
return "DIRECT"; // evenings and weekends go direct
dateRange(day1[opt], month1[opt], year1[opt], day2[opt], month2[opt], year2[opt])
→ boolean

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.

example
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
timeRange(hour1, min1[opt], sec1[opt], hour2, min2[opt], sec2[opt])
→ boolean

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.

example
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
alert(message)
→ void

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.

Never ship alert() calls in a production PAC file. Remove all debug calls before deploying.
example
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.

dnsResolve()
● High cost
Always triggers a live DNS lookup. Cache the result in a variable — never call twice for the same host.
isResolvable()
● High cost
Performs a DNS lookup on every call. Replace with dnsDomainIs() or shExpMatch() wherever possible.
isInNet() + hostname
● High cost
If the first argument is a hostname (not an IP), isInNet() internally calls dnsResolve(). Always pre-resolve.
isInNet() + IP
● No cost
If you pass a pre-resolved IP (from dnsResolve), no additional DNS lookup occurs. This is the correct pattern.
dnsDomainIs()
● No cost
Pure string suffix match. Zero DNS overhead. Always prefer this over isResolvable() for domain checks.
shExpMatch()
● No cost
Pure string glob match. Zero DNS overhead. Good for URL and host pattern matching.

The golden rule:

DNS optimisation pattern
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.

proxy.pac
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.

proxy.pac
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.

proxy.pac
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.

proxy.pac
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(), and shExpMatch() 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 every isInNet() call. Calling dnsResolve() or isResolvable() multiple times for the same host multiplies your DNS cost.
  • Guard DNS failures. dnsResolve() returns an empty string "" on failure, not null. Always check if (ip && ...) before passing to isInNet() — 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 accept application/x-javascript-config or text/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 DIRECT at 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. Use pacparser or browser DevTools for testing instead.
  • Use IP addresses in subnet patterns. In isInNet(host, pattern, mask), pattern must always be a dotted-decimal IP — never a hostname. Hostnames in pattern can fail silently across browsers.

Security

PAC files are a high-value attack target. Understand the risks before deploying.

A compromised or spoofed PAC file can silently redirect all browser traffic — including authenticated sessions — through an attacker-controlled proxy. Take PAC file security seriously.
  • 🔒
    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 use isInNet() 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.

1
DHCP first: The client sends a DHCP request on startup. If the DHCP server includes Option 252 (WPAD URL), the client uses that URL to fetch the PAC file directly. This is the fastest and most reliable method.
2
DNS fallback: If DHCP doesn't provide a WPAD URL, the client tries DNS. It constructs candidate URLs by prepending wpad. to the domain: e.g. http://wpad.corp.com/wpad.dat. The first hostname that resolves and serves a valid PAC file wins.
3
Configure your server: Host wpad.dat at the WPAD URL with MIME type application/x-ns-proxy-autoconfig. Ensure the hostname wpad.yourdomain.com resolves in internal DNS.
4
Browser settings: Enable "Automatically detect proxy settings" in the browser or OS proxy configuration. On Windows this is under Settings → Network → Proxy → Automatically detect settings.
WPAD is disabled by default in many modern browsers and OS configurations due to security risks (see Security). Consider configuring the PAC URL explicitly via Group Policy or MDM instead of relying on WPAD auto-discovery.

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.

bash — install and use pacparser
# 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.

firefox about:config + 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
PAC debug snippet (remove before deploying)
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.

chrome netlog
# 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.

SymptomCause & 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.

proxy.pac
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.

proxy.pac
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.

proxy.pac
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.

proxy.pac
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.

Click "Run PAC" to evaluate…