# Agent proxy installation

The existing [first-party proxy source](/docs/installation/first-party-proxy.md) provides Nginx, Apache and Cloudflare templates. Apply those templates only to domains you control. Keep the canonical site domain even when the tracker loads from a proxy:

```html
<script
  defer
  src="https://example.com/pf.min.js"
  data-domain="example.com"
  data-api="https://example.com/api/event"
></script>
```

The tracker derives `/api/tracker-config` from `data-api` unless an explicit `data-config-api` is supplied. Forward query strings and POST bodies unchanged. Use a fixed upstream allowlist, TLS verification and the upstream host; never turn a tracker proxy into a general URL-fetch endpoint.

## Forwarding and Caching

| Path | Purpose | Cache policy |
| --- | --- | --- |
| `/pf.min.js` | Base tracker | Public JavaScript, about one hour; respect upstream validators. |
| `/pf-vitals.min.js` | Core Web Vitals | Same asset policy when enabled. |
| `/pf-experiments.min.js` | Experiments | Same asset policy when enabled. |
| `/pf-events.min.js` | No-code events | Same asset policy when enabled. |
| `/pf-search.min.js` | Site search | Same asset policy when enabled. |
| `/pf-replay.min.js` | Session replay | Same asset policy when enabled. |
| `/api/tracker-config` | Current site settings | No cache, or very short upstream-approved caching keyed by the complete domain query. |
| `/api/event` | Pageview and event ingestion | Never cache requests or responses. |
| `/api/replay/chunks` | Optional replay ingestion | Never cache requests or responses. |

Do not use long-lived immutable caching for unversioned tracker assets. Do not cache failed asset responses as successful JavaScript. Forward the optional module paths from the same origin as the core tracker, and do not enable replay, experiments or another feature simply because its file can be fetched. Do not proxy OAuth or private machine APIs through a public tracker asset cache.

## Client IP Trust

Blindly forwarding visitor-supplied `X-Forwarded-For`, `X-Real-IP` or `CF-Connecting-IP` permits spoofing. At your first trusted ingress, strip untrusted forwarding and `X-PureStats-Proxy-*` headers and derive the visitor address from the trusted network connection or your explicitly trusted edge. Set a sanitized forwarding value when proxying to PureStats.

PureStats accepts a forwarded client IP only when the connecting proxy matches the site's configured trusted proxy IPs or CIDRs. An untrusted proxy falls back to its remote connection IP. If the site's proxy policy requires signatures, missing or invalid signatures reject the proxy request. Merely adding a forwarding header does not activate trust.

Signed forwarding uses `X-PureStats-Proxy-Domain`, `X-PureStats-Proxy-Client-IP`, `X-PureStats-Proxy-Timestamp` and `X-PureStats-Proxy-Signature`. The signature is HMAC-SHA256 over four newline-separated values: normalized lowercase canonical domain, trimmed client IP, decimal Unix timestamp and SHA256 of the exact raw request body. The signature window is five minutes. Sign only on a trusted server using the site's configured secret; keep that secret outside the worktree and browser. Preserve the body bytes used by the signature. A proxy secret is not an OAuth API credential.

## Verify Locally First

Use mocked upstream responses to check the allowed paths, methods, query strings, raw bodies, headers and cache rules. Check CSP against your real routing choice: a full first-party proxy can use `script-src 'self'` and `connect-src 'self'`; a directly loaded module still needs the upstream origin. Do not manufacture production pageviews for proxy tests. Run [installation checks](/docs/agents/testing.md) after an authorized deployment.
