Background
Packet loss measurement relies on a TURN server to relay UDP packets. Browsers cannot open raw UDP sockets, so all traffic is routed through the TURN relay, which lets the engine count packets that never return. Because TURN credentials must be short-lived to prevent abuse, the recommended pattern is a lightweight server-side worker that:- Holds your TURN API secret securely.
- Generates fresh credentials on each request.
- Returns them to the browser only from allowed origins.
example/turn-worker/ directory in the @cloudflare/speedtest repository contains a ready-to-deploy Cloudflare Worker that does exactly this, backed by Cloudflare Realtime TURN.
Cloudflare Realtime TURN servers are subject to billing after the free tier limits are reached. Review the TURN FAQ before deploying to production.
Setup steps
Create a Cloudflare Realtime TURN App
- In the Cloudflare Dashboard, select Realtime from the sidebar.
- Under TURN Server, click Create.
- Enter a name for your TURN app and click Create.
- Copy the Turn Token ID and API Token — you will need both in the next steps.
Configure local development credentials
Create a This file is read by Wrangler during local development with
.dev.vars file at the root of the turn-worker directory with your TURN app credentials:.dev.vars
npm run start. It is listed in .gitignore and should never be committed.Add remote secrets with Wrangler
Push the same credentials as encrypted secrets to your deployed worker:
Configure routes and allowed origins
Open To serve the worker from your own domain instead of the default
wrangler.jsonc and update the REALTIME_TURN_ORIGINS variable to list every origin that is allowed to request credentials:wrangler.jsonc
*.workers.dev subdomain, uncomment the routes section and replace turn.example.com and <YOUR_ZONE_ID> with your domain and zone ID.Deploy the worker
Worker source code
The complete worker is a single TypeScript file. It validates the request origin, calls the Cloudflare Realtime API to generate short-lived credentials, and returns only the UDP TURN URLs to the browser.src/index.ts
What the worker does
- Origin check — The worker reads the
Refererheader and rejects any request whose origin is not inREALTIME_TURN_ORIGINS. This prevents other sites from using your TURN quota. - Credential generation — It calls the Cloudflare Realtime REST API (
/v1/turn/keys/{id}/credentials/generate) with a short TTL (default 240 seconds) so credentials cannot be reused beyond a single test session. - UDP-only filter — The response filters the returned ICE server URLs to keep only
turn:addresses withtransport=udp, which is what the packet loss engine requires. - CORS header — The
access-control-allow-originresponse header is set to the exact requesting origin so browsers honour the response.
Credential response format
The SpeedTest engine expectsturnServerCredsApiUrl to return a JSON object with the following shape:
urls field is optional. If omitted, the engine uses the turnServerUri constructor option for the server address.
