Skip to main content
The public Cloudflare TURN server (turn.speed.cloudflare.com:50000) is deprecated and will be discontinued soon. You must provide your own TURN server configuration to receive packet loss results. See TURN server setup for step-by-step instructions.

What it measures

Packet loss is the ratio of UDP packets sent to a TURN server that never return, expressed as a value between 0 (no loss) and 1 (total loss). A ratio of 0.02, for example, means 2% of packets were lost. This metric is a direct signal of connection reliability. High packet loss degrades real-time applications such as video calls, gaming, and VoIP.

How it works

The engine opens a WebRTC data channel to a TURN server and sends a configurable number of UDP packets in a round-trip fashion. Each packet that is sent is expected to return. After all packets have been sent, the engine waits a configurable amount of time for any late-arriving responses. Packets that never return are counted as lost. Packets are sent in batches to avoid flooding the connection. The batch size and inter-batch wait time are both configurable.

Requirements

Packet loss measurement requires a TURN server that the browser can reach over UDP. You must supply either:
  • A credentials API URL (turnServerCredsApiUrl) pointing to an endpoint that returns short-lived credentials, or
  • A static username and password (turnServerUser / turnServerPass).
The recommended approach is the credentials API, backed by a Cloudflare Worker. See TURN server setup.

Constructor options

These options are passed when instantiating SpeedTest. They configure which TURN server to connect to and how to authenticate.
turnServerUri
string
default:"turn.speed.cloudflare.com:50000"
URI of the TURN server used for packet loss measurement. Provide your own server URI when the public one is deprecated.
turnServerCredsApiUrl
string
URL of an endpoint that returns short-lived TURN credentials as JSON. The response must include username and credential string fields, and may optionally include a urls array of TURN server addresses. Takes precedence over static credentials when provided. See TURN server setup for a reference implementation.
turnServerUser
string
Static TURN server username. Use when you are not using a credentials API.
turnServerPass
string
Static TURN server password. Use when you are not using a credentials API.

Measurement configuration

The packetLoss measurement type accepts the following fields inside the measurements array:
type
string
required
Must be "packetLoss".
numPackets
number
default:"100"
Total number of UDP packets to send. The default sequence uses 1000.
responsesWaitTime
number
default:"5000"
Milliseconds to wait after the last packet reception before closing the measurement and counting unreturned packets as lost. The default sequence uses 3000 ms.
batchSize
number
default:"10"
Number of packets per batch. If higher than numPackets, all packets are sent in a single batch.
batchWaitTime
number
default:"10"
Milliseconds to wait between batches.
connectionTimeout
number
default:"5000"
Milliseconds to wait for a successful TURN connection before aborting.

Default sequence entry

The default measurement sequence includes the following packet loss entry:
defaultConfig.js
{
  type: 'packetLoss',
  numPackets: 1e3,
  batchSize: 10,
  batchWaitTime: 10,       // ms between batches
  responsesWaitTime: 3000  // ms silent time after last sent message
}
You can override this by supplying your own measurements array in the constructor.

Basic setup

import SpeedTest from '@cloudflare/speedtest';

const engine = new SpeedTest({
  turnServerCredsApiUrl: 'https://your-worker.your-account.workers.dev/turn-credentials',
});

engine.onFinish = results => {
  const loss = results.getPacketLoss();
  console.log(`Packet loss: ${(loss * 100).toFixed(1)}%`);
};

Reading results

getPacketLoss()

Returns the packet loss ratio as a number between 0 and 1, or undefined if no packet loss measurement has completed.
const loss = results.getPacketLoss();
// e.g. 0.02 → 2% packet loss

getPacketLossDetails()

Returns the full detail object for the packet loss measurement, or undefined if no measurement has completed. On success, the shape is:
{
  packetLoss: number,       // ratio 0–1
  totalMessages: number,    // total packets expected
  numMessagesSent: number,  // packets actually sent
  lostMessages: number[]    // indices of packets that were lost
}
On error, the shape is:
{ error: string }
Example:
const details = results.getPacketLossDetails();

if (details && 'error' in details) {
  console.error('Packet loss measurement failed:', details.error);
} else if (details) {
  console.log(`Sent ${details.numMessagesSent} packets, lost ${details.lostMessages.length}`);
}

Listening for changes

Use onResultsChange to act on the packet loss result as soon as it is available:
engine.onResultsChange = ({ type }) => {
  if (type === 'packetLoss') {
    console.log('Packet loss:', engine.results.getPacketLoss());
  }
};

Error handling

If the engine cannot connect to the TURN server, or if credential fetching fails, the onError callback is invoked with a string describing the failure. The getPacketLossDetails() return value will contain an error field instead of measurement data.
engine.onError = error => {
  console.error('SpeedTest error:', error);
};
Common causes of errors:
  • The TURN server is unreachable from the client’s network.
  • The credentials endpoint returned an unexpected status or malformed JSON.
  • The connection timed out before the TURN handshake completed (see connectionTimeout).

Build docs developers (and LLMs) love