[{"data":1,"prerenderedAt":142},["ShallowReactive",2],{"doc:\u002Fdocs\u002Fproxies\u002Ftraefik":3},{"page":4,"toc":125,"updated":141},{"path":5,"title":6,"seoTitle":7,"description":8,"blocks":9},"\u002Fdocs\u002Fproxies\u002Ftraefik","Traefik proxy","Traefik First-Party Proxy for the Browser SDK","Route the one path the dashboard issued from Traefik to the small proxy container, with Docker labels, the file provider or a Kubernetes IngressRoute.",[10,13,18,20,77,81,84,86,91,93,101,103,106,108,113,116,119,121,123],{"type":11,"text":12},"p","Traefik's configuration alone cannot copy the visitor's address into a header: its headers middleware only sets fixed values. So this template routes the one path the dashboard issued to the [proxy container](\u002Fdocs\u002Fproxies\u002Fcontainer), which does the forwarding. The router answers exactly that path, on your own host. Version 1.0.0, reported as `container\u002F1.0.0` because the container runs it.",{"type":14,"level":15,"text":16,"id":17},"heading",2,"Set up","set-up",{"type":11,"text":19},"These steps use Traefik's Docker provider. The [file provider](#file-provider-and-kubernetes) and Kubernetes follow them.",{"type":21,"steps":22},"steps",[23,30,44,55,66],{"title":24,"blocks":25},"Create the proxy",[26,28],{"type":11,"text":27},"In the dashboard, open **Integration > Proxy keys** and choose **New proxy**. Pick the environment of the public key it will forward, and **Traefik** under **How it runs**. Copy the [proxy key](\u002Fdocs\u002Fproxy-integrations#proxy-keys) it shows once, and note the path it issued, such as `\u002Fk7x2qm4a`.",{"type":11,"text":29},"The dashboard then prints the steps below, filled in with your proxy's own path and key. They stay under **Show setup** in the proxy's menu, without the key, which is never shown again.",{"title":31,"blocks":32},"Save the service",[33,35,37],{"type":11,"text":34},"Save `server.mjs` as `site-relay\u002Fserver.mjs` beside your compose file, and its Dockerfile beside it as `site-relay\u002FDockerfile`. The image has no dependencies and runs as an unprivileged user.",{"type":11,"text":36},"`server.mjs` is a small Node service with no dependencies: the built-in HTTP server and `fetch`. It reads `FINGERLY_PROXY_KEY`, `FINGERLY_PATH`, `FINGERLY_CLIENT_IP_SOURCE` and `PORT` (default `8080`), and refuses to start when the address source is not one it knows. The dashboard prints it; copy it from there.",{"type":38,"samples":39},"code",[40],{"label":41,"lang":42,"code":43},"site-relay\u002FDockerfile","dockerfile","FROM node:24-alpine\nWORKDIR \u002Fapp\nCOPY server.mjs .\u002F\nENV NODE_ENV=production PORT=8080\nUSER 1000:1000\nEXPOSE 8080\nCMD [\"node\", \"server.mjs\"]",{"title":45,"blocks":46},"Add it to your compose project",[47,49],{"type":11,"text":48},"In the project Traefik routes with its Docker provider, on a network Traefik can reach. If your other routers name `entrypoints` or `tls` in their labels, add the same to this one.",{"type":38,"samples":50},[51],{"label":52,"lang":53,"code":54},"docker-compose.yml","yaml","services:\n  site-relay:\n    build: .\u002Fsite-relay\n    restart: unless-stopped\n    environment:\n      FINGERLY_PATH: \u002Fk7x2qm4a\n      FINGERLY_CLIENT_IP_SOURCE: header:x-real-ip\n      FINGERLY_PROXY_KEY: ${FINGERLY_PROXY_KEY:?set FINGERLY_PROXY_KEY}\n    labels:\n      - \"traefik.enable=true\"\n      - \"traefik.http.routers.site-relay.rule=Path(`\u002Fk7x2qm4a`)\"\n      - \"traefik.http.routers.site-relay.priority=10000\"\n      - \"traefik.http.services.site-relay.loadbalancer.server.port=8080\"",{"title":56,"blocks":57},"Start it",[58,60],{"type":11,"text":59},"Take the key from your secret store, or an `.env` file kept out of version control.",{"type":38,"samples":61},[62],{"label":63,"lang":64,"code":65},"Terminal","bash","FINGERLY_PROXY_KEY='fly_px_production_…' docker compose up -d --build site-relay",{"title":67,"blocks":68},"Point the browser SDK at the path",[69,75],{"type":38,"samples":70},[71],{"label":72,"lang":73,"code":74},"fingerly.ts","ts","import { load } from '@fingerly\u002Fweb-js'\n\nconst fingerly = await load({\n  apiKey: 'fly_pk_production_…',\n  endpoints: '\u002Fk7x2qm4a',\n})",{"type":11,"text":76},"The SDK posts both of its calls to the path exactly as given. Once pages use it, the proxy's row in **Proxy keys** shows it last seen, running `container\u002F1.0.0`, with its requests over the last 30 days.",{"type":78,"tone":79,"text":80},"callout","note","The router has priority `10000`. Traefik otherwise ranks routers by the length of their rule, so a longer catch-all rule for your site would win over the one-path rule.",{"type":14,"level":15,"text":82,"id":83},"File provider and Kubernetes","file-provider-and-kubernetes",{"type":11,"text":85},"With the file provider, run the container as `site-relay` on a network Traefik can reach, and add this to your dynamic configuration:",{"type":38,"samples":87},[88],{"label":89,"lang":53,"code":90},"dynamic.yml","http:\n  routers:\n    site-relay:\n      rule: \"Path(`\u002Fk7x2qm4a`)\"\n      priority: 10000\n      service: site-relay\n  services:\n    site-relay:\n      loadBalancer:\n        servers:\n          - url: \"http:\u002F\u002Fsite-relay:8080\"",{"type":11,"text":92},"On Kubernetes, push the image to your registry, create the key's Secret from your secret store, and apply a Deployment of it (`FINGERLY_PATH`, `FINGERLY_CLIENT_IP_SOURCE=header:x-real-ip`, and `FINGERLY_PROXY_KEY` from the Secret, running as non-root with a read-only root filesystem), a Service on port `8080`, and an `IngressRoute`:",{"type":38,"samples":94},[95,98],{"label":96,"lang":64,"code":97},"Secret","kubectl create secret generic site-relay --from-literal=proxy-key=fly_px_production_…",{"label":99,"lang":53,"code":100},"IngressRoute","apiVersion: traefik.io\u002Fv1alpha1\nkind: IngressRoute\nmetadata:\n  name: site-relay\nspec:\n  routes:\n    - match: Path(`\u002Fk7x2qm4a`)\n      kind: Rule\n      priority: 10000\n      services:\n        - name: site-relay\n          port: 8080",{"type":11,"text":102},"The dashboard prints the whole manifest, the Deployment and the Service included.",{"type":14,"level":15,"text":104,"id":105},"How the visitor's address is trusted","how-the-visitor-s-address-is-trusted",{"type":11,"text":107},"The container reads `X-Real-Ip` (`FINGERLY_CLIENT_IP_SOURCE=header:x-real-ip`). For a request from an address outside `forwardedHeaders.trustedIPs`, Traefik deletes every `X-Forwarded-*` and `X-Real-Ip` header the client sent and sets `X-Real-Ip` to the address of the connection it accepted, so a visitor cannot forge it. That is the default, with no trusted IPs configured.",{"type":109,"items":110},"list",[111,112],"**Traefik behind a load balancer** listed in `forwardedHeaders.trustedIPs`: Traefik keeps the `X-Real-Ip` the load balancer sent, and only sets it when there is none. Keep `header:x-real-ip` if the load balancer sets `X-Real-Ip` itself on every request. If it only appends to `X-Forwarded-For`, use `xff:2` instead: the load balancer appends the visitor, then Traefik appends the load balancer.","**Kubernetes**: Traefik sees the visitor's address only when its own Service preserves it, with `externalTrafficPolicy: Local` or the PROXY protocol from the load balancer. Otherwise every request carries a node's address.",{"type":78,"tone":114,"text":115},"warning","Never set `forwardedHeaders.insecure`. Traefik then keeps whatever the visitor sent.",{"type":14,"level":15,"text":117,"id":118},"What it sends and relays","what-it-sends-and-relays",{"type":11,"text":120},"Upstream, to `https:\u002F\u002Fapi.fingerly.io\u002Fapi\u002Fv1\u002Fingest` and nowhere else, on headers built from scratch: `content-type`, `x-api-key`, `Idempotency-Key` when present, and the `x-fingerly-*` headers the proxy sets itself (the proxy key, the visitor's address, the page origin, the user agent and `x-fingerly-proxy-version: container\u002F1.0.0`). Cookies, `Authorization` and any `x-fingerly-*` header a visitor sends never leave.",{"type":11,"text":122},"Other methods answer `405` with `Allow: POST`, and other paths `404`. Bodies over 1 MiB are refused with `413`, redirects are not followed, and a missing or malformed key answers `503` rather than forwarding anything. Traefik adds nothing to the response.",{"type":11,"text":124},"Back to the page: the API's status and body, with only `content-type`, `x-request-id`, `ratelimit-limit`, `ratelimit-remaining` and `retry-after`, plus `cache-control: no-store`. Nothing relayed names Fingerly.",[126,127,130,132,134,136,138,139,140],{"id":17,"text":16,"level":15},{"id":128,"text":24,"level":129},"step-create-the-proxy",3,{"id":131,"text":31,"level":129},"step-save-the-service",{"id":133,"text":45,"level":129},"step-add-it-to-your-compose-project",{"id":135,"text":56,"level":129},"step-start-it",{"id":137,"text":67,"level":129},"step-point-the-browser-sdk-at-the-path",{"id":83,"text":82,"level":15},{"id":105,"text":104,"level":15},{"id":118,"text":117,"level":15},"2026-09-25T18:31:31.000Z",1793630453199]