---
title: "Set Up Distributed Tracing"
description: "Learn how to connect events across applications/services using the Sentry JavaScript SDK."
url: https://docs.sentry.io/platforms/javascript/guides/cloudflare/tracing/distributed-tracing/
---

# Set Up Distributed Tracing | Sentry for Cloudflare

Distributed tracing connects and records the path of requests as they travel through the different tiers of your application architecture. If your architecture consists of multiple services that live on different sub-domains (e.g. `fe.example.com` and `api.example.com`), distributed tracing will help you follow the path of events as they move from one service to another.

This end-to-end visibility allows developers to identify bottlenecks, pinpoint the root cause of errors, and understand component interactions—turning what would be a complex debugging nightmare into a manageable process that improves system reliability and performance.

## [Basic Example](https://docs.sentry.io/platforms/javascript/guides/cloudflare/tracing/distributed-tracing.md#basic-example)

Here's an example showing a distributed trace in Sentry:

This distributed trace shows a Vue app's `pageload` making a request to a Python backend, which then calls the `/api` endpoint of a Ruby microservice.

What happens in the background is that Sentry uses reads and further propagates two HTTP headers between your applications:

* `sentry-trace`
* `baggage`

If you run any JavaScript applications in your distributed system, make sure that those two headers are added to your CORS allowlist and won't be blocked or stripped by your proxy servers, gateways, or firewalls.

## [How to Use Distributed Tracing?](https://docs.sentry.io/platforms/javascript/guides/cloudflare/tracing/distributed-tracing.md#how-to-use-distributed-tracing)

The Sentry Cloudflare SDK automatically propagates traces for incoming and outgoing HTTP requests if you've setup the SDK to send traces.

For RPC calls within Cloudflare (Worker-to-Durable Object, Worker-to-Worker via service bindings), you name the bindings that should carry the trace. See [RPC Trace Propagation](https://docs.sentry.io/platforms/javascript/guides/cloudflare/tracing/distributed-tracing.md#rpc-trace-propagation) below.

### [RPC Trace Propagation](https://docs.sentry.io/platforms/javascript/guides/cloudflare/tracing/distributed-tracing.md#rpc-trace-propagation)

Available since: `v10.52.0`

By default, traces are not propagated across [RPC calls](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc/) between Workers and Durable Objects. This is because [Cap'n Proto](https://capnproto.org/) (which powers Cloudflare RPC) has no native support for headers or metadata. The SDK carries the trace context in a trailing argument instead, and the receiving SDK strips it before your method runs.

That trailing argument is why propagation is opt-in per binding: only a Sentry-instrumented receiver strips it again. List the bindings whose receiver you know runs Sentry in `rpcTracePropagationBindings` (SDK version 10.72.0 or higher). Setting the option also turns on the receiver side, so a Worker that both calls and receives needs nothing else.

If you build with the [Sentry Cloudflare Vite plugin](https://docs.sentry.io/platforms/javascript/guides/cloudflare/features/vite-plugin.md) and its `autoInstrumentation` option, the plugin configures the bindings that point at classes in the same Worker. You only need to list bindings to other Workers.

**Worker Side (Caller):**

```typescript
import * as Sentry from "@sentry/cloudflare";

export default Sentry.withSentry(
  (env) => ({
    dsn: env.SENTRY_DSN,
    tracesSampleRate: 1.0,
    // Propagate to `env.MY_DURABLE_OBJECT` and every `env.SVC_*` binding
    rpcTracePropagationBindings: ["MY_DURABLE_OBJECT", /^SVC_/],
  }),
  {
    async fetch(request, env, ctx) {
      const id = env.MY_DURABLE_OBJECT.idFromName("test");
      const stub = env.MY_DURABLE_OBJECT.get(id);

      // This RPC call will now propagate trace context
      const result = await stub.sayHello("World");

      return new Response(result);
    },
  },
);
```

Strings match a binding name exactly, so `"DB"` does not also match `MY_DB`. Use a regular expression for pattern matching. For the full definition, see [`rpcTracePropagationBindings`](https://docs.sentry.io/platforms/javascript/guides/cloudflare/configuration/options.md#rpcTracePropagationBindings).

##### Only List Receivers You Control

A Worker that is not instrumented with Sentry never strips the trailing trace argument, so your method sees an extra parameter it was not called with. This is harmless for methods with fixed parameter lists, but changes behavior if your method uses rest parameters `(...args)` or reads `arguments.length`. Leave those bindings off the list.

**Durable Object Side (Receiver):**

The receiver continues the trace it is sent, creates a span per RPC method, and strips the trailing argument, so your method signatures are unaffected. A Durable Object that only receives opts in with an empty list.

```typescript
import * as Sentry from "@sentry/cloudflare";

class MyDurableObjectBase extends DurableObject<Env> {
  sayHello(name: string): string {
    return `Hello, ${name}!`;
  }
}

export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
  (env: Env) => ({
    dsn: env.SENTRY_DSN,
    tracesSampleRate: 1.0,
    // Receive traces over RPC, but propagate to nothing when calling out
    rpcTracePropagationBindings: [],
  }),
  MyDurableObjectBase,
);
```

Listing bindings here does no harm, so you can reuse the caller's list and share one options object across the whole deployment. Every name on it already points at a Sentry receiver, and a binding the Durable Object never calls is simply never matched.

### [Custom Instrumentation](https://docs.sentry.io/platforms/javascript/guides/cloudflare/tracing/distributed-tracing.md#custom-instrumentation)

If you don't want to use the default tracing setup, you can set up [Custom Instrumentation](https://docs.sentry.io/platforms/javascript/guides/cloudflare/tracing/trace-propagation/custom-instrumentation.md) for distributed tracing.

### [Disabling Distributed Tracing](https://docs.sentry.io/platforms/javascript/guides/cloudflare/tracing/distributed-tracing.md#disabling-distributed-tracing)

If you want to disable distributed tracing, set the `tracePropagationTargets` option to be an empty array. This will ensure no trace headers are sent.

```javascript
Sentry.init({
  dsn: "https://<key>@o<orgId>.ingest.sentry.io/<projectId>",
  // Capture 100% of spans. This is useful for development and debugging. Consider reducing in production or using traceSampler
  tracesSampleRate: 1.0,
  // Overwrite the defaults to ensure no trace headers are sent
  tracePropagationTargets: [],
});
```

### [Trace Propagation Examples](https://docs.sentry.io/platforms/javascript/guides/cloudflare/tracing/distributed-tracing.md#trace-propagation-examples)

#### [Example 1: Microservices E-commerce Platform](https://docs.sentry.io/platforms/javascript/guides/cloudflare/tracing/distributed-tracing.md#example-1-microservices-e-commerce-platform)

```javascript
Sentry.init({
  dsn: "https://<key>@o<orgId>.ingest.sentry.io/<projectId>",
  // Capture 100% of spans. This is useful for development and debugging. Consider reducing in production or using traceSampler
  tracesSampleRate: 1.0,
  tracePropagationTargets: [
    "https://api.myecommerce.com",
    "https://auth.myecommerce.com",
  ],
});
```

This tells Sentry to pass trace headers across the following paths:

* Your main API server (where product data comes from)
* Your authentication server (where logins happen)

This way, if a customer experiences an error during checkout, or you want to check the performance of a specific endpoint, you can see the complete path their request took across these different services.

#### [Example 2: Mobile App with Backend Services](https://docs.sentry.io/platforms/javascript/guides/cloudflare/tracing/distributed-tracing.md#example-2-mobile-app-with-backend-services)

```javascript
Sentry.init({
  dsn: "https://<key>@o<orgId>.ingest.sentry.io/<projectId>",
  tracePropagationTargets: [
    "https://api.myapp.com",
    "https://media.myapp.com",
    /^\/local-api\//,
  ],
});
```

This configuration lets your app track user actions across:

* Your main API server (handles most app functions)
* Your media server (handles images, videos, etc.)
* Any local API endpoints in your app

If your app crashes while a user is uploading a photo, you can trace exactly where the problem occurred - in the app itself, the main API, or the media service.

Remember that in order to propagate trace information through your whole distributed system, you have to use Sentry in all of the involved services and applications. Take a look at the respective SDK documentation to learn how distributed tracing can be enabled for each platform.

## [Trace Duration](https://docs.sentry.io/platforms/javascript/guides/cloudflare/tracing/distributed-tracing.md#trace-duration)

Server-side SDKs handle traces automatically on a per-request basis. This means that SDKs will:

* Continue an existing trace if the incoming request contains a trace header.
* Start a new trace if the incoming request does not contain a trace header. This trace stays active until the response is sent.

If necessary, you can override the default trace duration by [manually starting a new trace](https://docs.sentry.io/platforms/javascript/guides/cloudflare/tracing/distributed-tracing/custom-instrumentation.md#starting-a-new-trace).

## [How Sampling Propagates in Distributed Traces](https://docs.sentry.io/platforms/javascript/guides/cloudflare/tracing/distributed-tracing.md#how-sampling-propagates-in-distributed-traces)

Sentry uses a "head-based" sampling approach:

* A sampling decision is made in the originating service (the "head")
* This decision is propagated to all downstream services

The two key headers are:

* `sentry-trace`: Contains trace ID, span ID, and sampling decision
* `baggage`: Contains additional trace metadata including sample rate

Sentry automatically attaches these headers to outgoing HTTP requests when using the `browserTracingIntegration`. For other communication channels like WebSockets, you can manually propagate trace information:

```javascript
// Extract trace data from the current scope
const traceData = Sentry.getTraceData();
const sentryTraceHeader = traceData["sentry-trace"];
const sentryBaggageHeader = traceData["baggage"];

// Add to your custom request (example using WebSocket)
webSocket.send(
  JSON.stringify({
    message: "Your data here",
    metadata: {
      sentryTrace: sentryTraceHeader,
      baggage: sentryBaggageHeader,
    },
  }),
);
```

## Pages in this section

- [Custom Trace Propagation](https://docs.sentry.io/platforms/javascript/guides/cloudflare/tracing/distributed-tracing/custom-instrumentation.md)
- [Dealing with CORS Issues](https://docs.sentry.io/platforms/javascript/guides/cloudflare/tracing/distributed-tracing/dealing-with-cors-issues.md)
