How Does Baggage Propagate Across Services in OpenTelemetry?
Follow a small set of key and value pairs as it rides along with a request from service to service, learn exactly how it is packed, sent, and unpacked, and discover the limits, risks, and good habits that keep it useful and safe.
Imagine arriving at a large music festival. At the gate, a volunteer snaps a coloured wristband on your wrist. It might say “VIP”, or “Staff”, or “Day pass”. From then on, every stall, every entrance, and every guard you meet can glance at your wrist and instantly know how to treat you, without calling the gate to ask. Nobody needs to look you up in a database, and you do not need to explain yourself at every door. The information simply travels with you.
Modern software systems face the same challenge. A single click from a customer can pass through a dozen services, and each one may need to know a few simple facts about the request: which customer it belongs to, which experiment it is part of, or which region it started in. OpenTelemetry solves this with a feature called baggage. In this guide we will build up the idea slowly, watch a request travel across services step by step, and learn how to use baggage without causing performance or security trouble.
1Why Services Need to Share Context
The problem baggage was created to solve.
In a monolith, an application is one big program. If the code at the top of a request knows the customer’s name, any function deeper in the same program can simply read it from a shared variable or an object passed along. When the same application is split into many small services running on different machines, that shared memory disappears. Each service only sees the message that arrived at its own door, and anything the earlier service knew is gone unless someone deliberately sends it along.
There are many facts that are interesting to every service in the chain but that do not naturally belong in the business message itself. Think of the identifier of the tenant in a multi-tenant product, the name of an experiment variation a user was assigned, a flag that says the request came from an internal test, or the geographic region where the request first entered the system. Changing every API in every service to carry these extra fields would be slow, error-prone, and would clutter the contracts between teams.
Think of a relay race where runners hand over a baton. The baton is the request itself. Now imagine each runner also clips a small tag to the baton, such as “team blue” or “lane three”. The next runner does not have to ask anyone what team they belong to, because the tag travelled with the baton. Baggage is that small tag, and propagation is the act of keeping it clipped on as the baton moves along.
Distributed tracing already has a related trick
OpenTelemetry already solves a similar problem for tracing. To connect the spans produced by different services into one trace, each service passes a small trace context to the next, containing the trace identifier and the parent span identifier. This is called context propagation. Baggage reuses the same machinery, so once you understand how the trace context hops from service to service, you already understand most of how baggage hops too. The difference is in what is carried and why.
Baggage is not a log, not a database, and not a security token. It is a small bundle of labels that travels with a request. Every idea in this guide comes back to that one sentence.
2What Exactly Is Baggage?
A set of name and value pairs that belongs to the request, not to any one service.
Baggage is a collection of key and value pairs, called entries, that is attached to the current context of a request and made available to every service the request touches. A key might be a short name like tenant.id and the value might be acme-corp. Both are strings. Any service in the chain can read the entries, add new ones, change values, or remove entries. Whatever the final set looks like is what gets forwarded to the next service.
Name
The key of the entry. It should be short, stable, and well documented across teams so that everyone interprets it the same way.
Value
The string that goes with the name. Values are encoded when sent over the network so that special characters do not break the format.
Metadata
An optional extra string attached to an entry, called properties in the wire format. The OpenTelemetry API treats it as opaque text that you may use to describe the entry.
Context
The container that holds baggage while a request is being processed inside one service. It also holds the active span and other cross-cutting data.
Baggage is not the same as attributes
New learners often confuse baggage with span attributes. An attribute is a label recorded on a piece of telemetry, such as a span, to describe it for later analysis. It is written once and shipped to the observability backend. Baggage, by contrast, is a label that travels forward with the request so that downstream code can see it while the request is still alive. Baggage entries do not turn into attributes by themselves. If you want them to appear on spans or logs, you have to copy them there on purpose, a topic we cover in Chapter 11.
| Property | Baggage entry | Span attribute |
|---|---|---|
| Purpose | Share facts with downstream services | Describe one span for later analysis |
| Travels to other services | Yes, in request headers or message metadata | No, it goes only to the telemetry backend |
| Readable by application code | Yes, through the baggage API | Generally not read back by application code |
| Appears in the backend automatically | No | Yes |
Setting a baggage entry does not make it show up in your tracing tool. Baggage is delivered to code running inside other services, not to your dashboards. People often add a baggage entry and then wonder why they cannot search for it in their trace backend.
3Context: The Carrier Inside One Service
Before baggage can cross the network, it first has to live somewhere inside a single process.
Inside one service, OpenTelemetry keeps request-scoped information in an object called the context. You can picture the context as an invisible backpack that follows the current unit of work. The active span lives in this backpack. Baggage lives in this backpack too. Functions deep inside your program can ask for the current context and look inside without anyone having to pass the backpack through every method call explicitly.
How does the backpack stay with the work? That depends on the language. In some languages it is attached to the current thread. In others it follows an asynchronous call chain through a special storage mechanism the runtime provides. In languages without such a mechanism, programmers pass the context explicitly as a parameter. The OpenTelemetry Context API hides most of these differences behind common operations for getting the current context, attaching a new one, and detaching it when the work is done.
Contexts are treated as immutable
A key design choice is that baggage is not edited in place. When you set a new entry, the API gives you back a new context that includes the change, and you then make that new context the current one. The old context is unchanged. This sounds fussy, but it prevents a whole class of bugs where one piece of code accidentally modifies the baggage seen by another piece of code running at the same time.
Read the current context
Code asks the Context API for the active context, which contains whatever baggage already arrived or was set earlier in this request.
Create a changed copy
Using the baggage API, code adds, updates, or removes an entry, which produces a new context holding the modified baggage.
Make the copy current
Code attaches the new context for the duration of the work, so everything called inside that scope sees the updated baggage.
Restore on exit
When the scope ends, code detaches the new context, and the previous context becomes current again, as though nothing had happened.
Think of a shared recipe card pinned in a kitchen. Instead of crossing out lines on the original, every cook who wants to change something photocopies the card, edits the copy, and pins the copy on top. When the cook leaves, the copy is taken down and the previous card is visible again. Nobody ruins a card another cook is reading.
Moving baggage between services is really a two-part trip. The first part is staying inside the context within one process. The second part is leaving the process through a network call and arriving in the context of another process. The rest of this guide is about that second part.
4Propagators: The Inject and Extract Dance
Two small operations move context across a process boundary.
A context cannot travel over a network as it is, because it is an in-memory object that only makes sense inside one process. To cross a boundary, the useful parts must be turned into text and placed on the outgoing message, and on the other side that text must be turned back into a context. The component that does both jobs is called a propagator.
flowchart LR
A["Context with baggage in Service A"] --> B["Propagator inject"]
B --> C["Carrier such as request headers"]
C --> D["Network"]
D --> E["Carrier arrives at Service B"]
E --> F["Propagator extract"]
F --> G["Context with baggage in Service B"]
Inject
Runs on the sending side. It reads the baggage from the current context and writes it into the outgoing carrier, for example as a header on an HTTP request.
Extract
Runs on the receiving side. It reads the incoming carrier, rebuilds the baggage, and returns a new context that contains it.
Carrier
The thing that physically carries the data, such as a set of HTTP headers, gRPC metadata, or message properties in a queue.
Getter and Setter
Tiny adapters that teach the propagator how to read from and write to a particular kind of carrier without knowing its details.
Why a separate propagator at all
The propagator is a plug-in point. Because the instrumentation libraries only know how to ask a propagator to inject and extract, they work with any wire format. The format used for baggage is the W3C Baggage standard, handled by a propagator often named the W3C baggage propagator. A different propagator could be swapped in without touching the instrumentation, which is how OpenTelemetry stays compatible with older formats from other tracing systems.
Who calls inject and extract
In most real applications, you never call these operations yourself. Instrumentation libraries for web frameworks and HTTP clients do it for you. When your service makes an outgoing call through an instrumented client, the library calls inject. When an incoming request reaches an instrumented server, the library calls extract and makes the resulting context current before your handler runs. That is why baggage often works with no extra code beyond setting and reading the entries.
Inject is like writing the address and notes on an envelope before posting it. Extract is like the receiving office opening the envelope and copying the notes into their own file. The envelope is only the transport, and the file is what the staff actually use.
5Baggage on the Wire: The W3C Header
What actually travels between services when baggage is injected.
For HTTP, baggage travels in a header simply named baggage. The value of the header is a list of entries separated by commas. Each entry is a name, an equals sign, and a value. An entry may also have extra properties, added after a semicolon. This format is defined by a W3C specification so that services built with different languages, libraries, and even different vendors can all read each other’s baggage.
Entries are separated by commas
The header holds a flat list, so a request with three baggage entries has one header with three comma-separated parts.
Each entry is name equals value
The name sits on the left and the value on the right, with an equals sign between them and no spaces that matter.
Values are percent-encoded
Characters that would confuse the format, such as commas, semicolons, spaces, and non-ASCII letters, are escaped using percent encoding and decoded again on arrival.
Properties follow a semicolon
An optional piece of metadata can follow an entry after a semicolon. Receivers that do not understand it are expected to pass it along untouched.
The size limits you should know
The W3C specification describes limits that every implementation is expected to support at minimum: up to 180 name and value pairs, up to 4096 bytes for a single pair, and up to 8192 bytes for the whole header. If a request carries more than that, implementations may drop entries to stay inside the limits. In practice, web servers, proxies, and gateways often impose their own header size limits, so a very large baggage header may be rejected well before it reaches the specification limit.
The specification limits are ceilings, not targets. Real systems should keep baggage to a handful of tiny entries. A header that is technically legal can still hurt performance, blow past a proxy limit, or leak more than you intended.
HTTP allows the same header to be sent more than once, and intermediaries may merge them. Well-behaved baggage receivers combine all copies of the baggage header into one logical list, so you should not assume that there is only a single header line on arrival.
6Following One Request Across Three Services
A story that shows every step of propagation in order.
Let us follow a request through an online store. A customer taps the Buy button. The request lands first at an edge gateway, which forwards it to an orders service, which then calls a payments service. The gateway knows the customer’s subscription tier and the region the request came from, and wants the later services to know too.
sequenceDiagram
participant U as Customer
participant G as Gateway
participant O as Orders service
participant P as Payments service
U->>G: Buy request, no baggage
Note over G: Sets tier and region in baggage
G->>O: Request plus baggage header
Note over O: Extracts baggage, adds experiment name
O->>P: Request plus extended baggage header
Note over P: Extracts baggage, reads tier, region, experiment
P-->>O: Response without baggage
O-->>G: Response without baggage
G-->>U: Final response
The gateway creates baggage
It looks at the customer’s account, finds the subscription tier and region, and records both in the baggage of the current context.
The gateway injects it
When the gateway’s HTTP client calls the orders service, the instrumentation injects the baggage into the outgoing header.
Orders extracts and adds
The orders server instrumentation extracts the header and rebuilds the baggage. The orders code reads the tier and also adds an entry naming the experiment the customer belongs to.
Orders injects the larger set
On its outgoing call to payments, the instrumentation injects the full set of entries, which now has three items instead of two.
Payments uses what arrived
The payments service can read all three entries and, for instance, choose a faster fraud check for premium customers, without ever calling the gateway to ask who the customer is.
Two lessons hiding in this story
First, propagation is transitive. A service that receives baggage and then makes its own outgoing calls will pass the baggage along automatically, along with any changes it made. The middle service did not have to know that payments existed in order to help payments receive the data. Second, propagation is one directional. The response headers going back up the chain do not carry baggage by default. If orders adds something, the gateway does not learn about it through baggage.
7Baggage, Trace Context, and Span Attributes Compared
Three related ideas that are easy to mix up.
When a request travels across services, several different kinds of data may ride along or be recorded. The trace context is the data that stitches spans together into one trace. Baggage is the user-defined data that rides along for the benefit of the application. Span attributes are the descriptions recorded on telemetry. They are cousins, but they have different jobs and different rules.
| Aspect | Trace context | Baggage | Span attributes |
|---|---|---|---|
| Main purpose | Link spans into one trace | Share application facts downstream | Describe telemetry for analysis |
| Who defines the content | The tracing standard | You and your teams | You and instrumentation libraries |
| Wire header | traceparent and tracestate | baggage | None, sent to the backend instead |
| Crosses service boundaries | Yes | Yes | No |
| Visible to application code | Rarely needed | Yes, by design | Not typically read back |
| Needs tracing enabled | Yes | No, it can work independently | Yes, when attached to spans |
Baggage works even when a trace is not sampled
Tracing systems often sample, which means only a fraction of requests have their spans recorded. The sampling decision is part of the trace context. Baggage is separate. Even when a request is not sampled, the baggage still travels, because it exists for the application and not for the trace backend. This is why baggage can power features such as routing or experiments reliably for every request, while spans are only kept for some of them.
Baggage does not need a trace at all
Because baggage rides in its own header and its own part of the context, you can use it in a system that has tracing turned off, as long as the baggage propagator is configured. Many teams use this to build lightweight cross-service signals without adopting full tracing.
Imagine a parcel moving through a courier network. The tracking number stitches the journey together, and that is the trace context. A sticker saying “fragile, deliver before noon” is baggage, because every depot reads it and acts on it. The courier’s internal notes about how long each depot took are the span attributes, which only the head office reads later.
If you find yourself wanting baggage only so that your trace backend can show a label, you probably want a span attribute instead. Use baggage when downstream code needs to read the value while the request is in flight.
8Configuring Propagation Correctly
Baggage only crosses a boundary if the propagator is turned on at both ends.
A frequent source of confusion is that baggage seems to vanish between services for no reason. In most cases the cause is configuration, not a bug. For baggage to survive a hop, the sending service must have a propagator that knows how to inject baggage, and the receiving service must have a propagator that knows how to extract it. If either side is missing, the chain breaks silently.
Composite propagators
Real systems usually need more than one propagator at once, for example one for trace context and one for baggage. OpenTelemetry supports this through a composite propagator that runs several propagators in order, each handling its own header. The standard configuration lists the propagators to use, and the specification names the default list as the trace context propagator followed by the baggage propagator. Many SDKs read this list from an environment variable named OTEL_PROPAGATORS, so operators can change it without touching code.
tracecontext
Handles the W3C trace context headers that connect spans across services.
baggage
Handles the W3C baggage header that carries your key and value pairs.
Legacy formats
Other propagators exist for older tracing headers, which helps during migrations when services use different formats.
none
Turns propagation off entirely, which stops both trace context and baggage from crossing boundaries.
Auto-instrumentation versus manual wiring
When you use automatic instrumentation, such as an agent that attaches to your application, the HTTP and messaging libraries are already wrapped, and inject and extract are performed for you using the configured propagators. When you write instrumentation by hand, or when you use a library that has no instrumentation, you must call the propagator yourself at the boundary, supplying the carrier and its getter and setter.
Check the propagator list on every service
Confirm that baggage is present in the configured list on both the caller and the callee, not only on one of them.
Confirm the boundary is instrumented
Make sure the specific client and server libraries used for the hop are covered by instrumentation, or call the propagator manually.
Inspect the real headers
Look at what actually leaves the caller and what arrives at the callee, using a debugging proxy or logs, to see whether the baggage header is present.
Check the infrastructure in between
Gateways, load balancers, and service meshes can strip, rename, or truncate headers, so verify their rules too.
In a large organisation, some services may be configured with only the trace context propagator. Baggage will reach such a service only if it extracts it, and it will not be forwarded further from there unless that service also injects it. One misconfigured service in the middle can cut the chain for everything downstream.
9Beyond HTTP: Other Ways Baggage Crosses Boundaries
The same inject and extract idea works for every kind of carrier.
HTTP is the most common path, but services also talk through remote procedure calls, message queues, event streams, and scheduled jobs. The propagation idea is the same in each case. The propagator needs a carrier that can hold text labels, and a getter and setter that know how to use it.
Remote procedure calls
Frameworks such as gRPC carry key and value pairs called metadata with each call. Instrumentation injects baggage into this metadata on the client and extracts it on the server, using the same baggage format as HTTP.
Message queues and event streams
Messaging systems offer message headers or properties. A producer injects baggage into those headers when publishing, and a consumer extracts it when processing, which lets baggage cross time gaps that may span seconds or hours.
Browser to backend
A web page can send baggage in the headers of requests it makes to your backend. For cross-origin requests, the server must explicitly allow the baggage header through its cross-origin sharing rules, or the browser will refuse to send it.
Serverless and batch jobs
Functions triggered by events and jobs started by schedulers can receive baggage through the event envelope, if the sender put it there. Without that, each invocation starts with an empty baggage.
Time gaps change the meaning
When a message waits in a queue for an hour, the baggage that travelled with it was true an hour ago. A feature flag, an experiment assignment, or a region may have changed since. Consumers should treat queued baggage as a snapshot of the moment the message was created, not as a live reading.
Fan-out and fan-in
When one request fans out to several downstream services, each branch receives a copy of the same baggage, and the branches may then change their copies independently. When results are merged back, there is no automatic merging of baggage. Each branch’s changes stay inside that branch, which keeps behaviour predictable but means you cannot rely on baggage to collect results.
Wherever a carrier can hold small text labels, baggage can ride on it. Wherever a hop is not instrumented, baggage stops, so every new communication path needs a deliberate check.
10Where Baggage Gets Lost
A tour of the usual suspects when an entry does not arrive.
Baggage can be lost at two kinds of places. Inside a process, it can be lost when the context fails to follow the work. Between processes, it can be lost when a header does not make it through. Knowing the usual suspects turns a mysterious problem into a short checklist.
flowchart TD
A["Service A sets baggage"] --> B{"Context follows the work inside A?"}
B -->|No| X1["Lost in thread pool or async hop"]
B -->|Yes| C{"Propagator injects baggage?"}
C -->|No| X2["Lost by configuration"]
C -->|Yes| D{"Intermediary keeps the header?"}
D -->|No| X3["Stripped or truncated by proxy"]
D -->|Yes| E{"Receiver extracts baggage?"}
E -->|No| X4["Lost by missing server instrumentation"]
E -->|Yes| F["Baggage arrives in Service B"]
Thread pools and async work
When work is handed to another thread or scheduled for later, the context may not travel unless the runtime or a wrapper copies it across.
Manual context passing
In languages that require explicit context parameters, forgetting to pass the context to a function quietly drops the baggage.
Uninstrumented clients
A hand-made network call that no instrumentation covers will not inject baggage unless the code does it.
Intermediaries
Gateways, load balancers, and service meshes may drop unknown headers, rename them, or cut them at a size limit.
Limit overflow
Too many entries or too many bytes can make implementations drop entries to fit within limits.
Encoding errors
Values with special characters that were not encoded properly may be rejected or cut off by a strict parser.
Propagation failures rarely raise errors. The request still succeeds, just without the labels. That is why you should test baggage flow end to end in a realistic environment, including every proxy, rather than trusting that it works because the unit tests pass.
11Getting Baggage Into Your Telemetry
Baggage reaches your code by default, but it only reaches your dashboards if you send it there.
Because baggage is not automatically copied to spans, logs, or metrics, you need a deliberate step if you want those labels to be searchable in your observability tools. The most common approach is a span processor, a small component that runs whenever a span is started and copies chosen baggage entries onto the span as attributes. Contributed implementations of this idea exist for several languages, and teams can also write their own.
A span starts
When a new span is created, the processor is invoked and can see the context that was current at that moment, including its baggage.
The processor filters
It selects only the baggage keys on an approved list, so that private or noisy entries are never copied.
The processor copies
Chosen entries are written to the span as attributes, so every span in every service carries the same business label.
The backend can now search
Analysts can filter traces by the label, for example to see only the requests from one tenant or one experiment arm.
Logs and metrics need care
The same idea can be applied to log records, so that every log line carries the tenant or experiment name. Metrics are different. Each distinct value of an attribute creates a new time series, so copying a high-variety baggage value into a metric can multiply storage cost sharply. Reserve metric labels for baggage with a small, fixed set of values, such as a subscription tier with three levels.
| Destination | Value of copying baggage | Main risk |
|---|---|---|
| Spans | Filter and group traces by business label | Leaking private values into trace storage |
| Logs | Correlate log lines with tenant or experiment | Leaking private values into log storage |
| Metrics | Break down rates and latencies by a small label | Cardinality explosion and higher cost |
When copying baggage to telemetry, list the keys that are allowed rather than trying to list the keys that are forbidden. New entries added by other teams next quarter will then stay out of your telemetry until someone reviews them.
12Size, Fan-out, and the Performance Bill
Baggage is small per request, but it is paid for on every hop.
It is tempting to think that a few hundred bytes of baggage is nothing. A single hop does not notice it. But baggage is attached to every outgoing request in a call chain, and the number of requests in a busy system is enormous. The cost is the size of the header multiplied by the number of calls, repeated millions of times per day.
Take a modest example. If a baggage header is 500 bytes and a single user action triggers twelve internal calls, that action sends about six kilobytes of baggage across the network. If the system handles ten thousand such actions every second, the baggage alone adds around sixty megabytes per second of header traffic, before counting any retries. That traffic costs bandwidth, adds a little latency per hop, and uses processing time to encode and decode.
Fan-out multiplies the cost
When one service calls twenty others in parallel, it sends twenty copies of the same baggage. When each of those calls twenty more, the copies multiply again. Wide call trees are common in recommendation, search, and aggregation systems, so baggage size is felt most in exactly the places where traffic is heaviest.
Header limits downstream
Every web server and proxy has a maximum total header size. Trace context, authentication tokens, cookies, and baggage all share that budget. Baggage that grows over time can quietly push total headers past a limit and cause requests to be rejected with confusing errors that look unrelated to telemetry.
Advantages
- No changes to business APIs when sharing a new label
- Works across languages, because the format is a standard
- Passes through services that do not understand it, if they forward it
- Available for every request, regardless of trace sampling
- Lets downstream teams act on upstream knowledge without extra calls
Disadvantages / Trade-offs
- Adds bytes to every hop of every request
- Can leak data to services and third parties that should not see it
- Can be forged by outside callers if not sanitised at the edge
- Fails silently when propagation is misconfigured
- Invites misuse as a hidden data channel between teams
13Security and Privacy: Handle With Care
Anything in baggage travels widely, so treat it like a postcard, not a sealed letter.
The most important safety fact about baggage is that it travels in plain text headers to every service that the request touches, and to every service those services call. If an instrumented service calls an external payment provider, a mapping service, or any other third party, the baggage header may go with it, unless you deliberately stop it. Everyone along that path can read every entry.
Passwords, access tokens, session identifiers, personal details such as names, emails, or phone numbers, payment details, and anything else that would cause harm if read by a stranger. Use opaque internal identifiers when you need to refer to a person or account.
Outbound leakage
When services call outside your organisation, check whether the HTTP client instrumentation also injects baggage into those calls. If so, third parties receive your internal labels. Teams usually prevent this by configuring propagation to apply only to approved destinations, by using an egress gateway that removes the header, or by using a client without baggage injection for external calls.
Inbound forgery
The reverse is just as important. Any outside caller can add a baggage header to a request sent to your public endpoint. If your services believe the incoming baggage blindly, an attacker could claim to be a premium customer or an internal test. The edge of your system, where the public meets the private, should either discard incoming baggage from untrusted sources or validate it before allowing it to continue.
Problem
Services read a baggage entry such as a customer tier or an admin flag and use it to decide what the caller is allowed to do.
Why It’s Harmful
Baggage can be set by anyone who can send a request, so it is not proof of identity or permission. An attacker who adds the header gains privileges the system never intended to give.
Correct Approach
Use baggage only for hints and labels. Base authorisation decisions on verified credentials and server-side lookups, and strip or re-create baggage at the trust boundary.
| Boundary | Threat | Typical control |
|---|---|---|
| Public edge, inbound | Forged or oversized baggage | Drop, validate, or re-create baggage at the gateway |
| Internal service to service | Accidental exposure to teams that do not need the data | Document each key and its audience |
| Outbound to third parties | Leaking internal labels or identifiers | Disable propagation or strip the header at egress |
| Telemetry storage | Private values copied into traces or logs | Allow list of keys that may be copied |
14Real-World Uses of Baggage
Where teams actually find baggage valuable.
Baggage shines when a small fact known early in a request would otherwise have to be rediscovered, again and again, deep in the system. The following examples describe common patterns used in the industry. Each one relies on the same mechanism you have already learned: set at the edge, propagate downstream, read where it matters.
Multi-tenant attribution
A software-as-a-service company sets an opaque tenant identifier at the gateway. Every service downstream can then group its telemetry by tenant, which helps support teams answer which customer is affected by a slow database without extra lookups.
Experiment and feature flag consistency
An experimentation platform assigns a user to an experiment arm once, at the start. Putting the arm name in baggage means every service sees the same assignment for the whole request, avoiding contradictory results where one service shows the new design and another shows the old one.
Cost attribution
Platform teams tag requests with the product or team that originated them. Deep shared services, such as storage or machine learning inference, can then charge their usage back to the true originator instead of to the immediate caller.
Test traffic marking
Synthetic checks and load tests add a marker that says the request is artificial. Downstream services can skip sending real emails or charging real cards, and analytics can exclude that traffic from business reports.
Canary and debug routing
An engineer investigating a problem may set a baggage entry that asks every service to record extra detail for that single request. Because the label travels with the request, the extra logging happens along the whole path, and only for that request.
Priority hints
A request from a premium or critical workflow carries a priority hint so queueing services and rate limiters can treat it kindly, while still verifying the real entitlement through proper checks.
A good baggage candidate is small, safe to show to any internal service, known at the start of the request, useful to many services, and not worth adding to every API by hand. If a candidate fails any of those checks, look for another approach.
15Design Patterns and Anti-patterns
Habits that keep baggage healthy, and traps to stay out of.
flowchart TD
A["Do many downstream services need this fact?"] -->|No| B["Use a normal API parameter"]
A -->|Yes| C{"Is it safe for every internal service to read?"}
C -->|No| D["Do not use baggage"]
C -->|Yes| E{"Is it small and low variety?"}
E -->|No| F["Pass an opaque identifier and look up details"]
E -->|Yes| G["Use baggage and document the key"]
Patterns that work
Set once at the edge
Create baggage in one well-owned place, usually the gateway, so every request starts with a consistent, trusted set.
Document every key
Keep a central list of baggage names, meanings, allowed values, and audiences so teams do not invent clashing keys.
Opaque identifiers
Carry short identifiers rather than descriptive data. A downstream service that truly needs details can look them up securely.
Copy through allow lists
Promote only reviewed keys into spans and logs, and only low-variety keys into metrics.
Sanitise at trust boundaries
Remove or re-create baggage where traffic enters from, or leaves to, parties you do not control.
Anti-patterns to avoid
Problem
Baggage is used as a general-purpose data channel, carrying whole user profiles, shopping carts, or other large pieces of data from service to service.
Why It’s Harmful
Every hop pays for the extra bytes, headers may hit size limits, and sensitive data is spread to services that never needed it. Services also begin to depend on hidden fields that no API contract describes.
Correct Approach
Put real business data in the API payload or a data store, and keep baggage for tiny labels such as identifiers and flags.
Problem
Every baggage entry is automatically copied into every span, log, and metric label, with no filtering.
Why It’s Harmful
Private data lands in long-lived storage, and high-variety values multiply metric series, driving up both risk and cost. A single new baggage key from another team can change your bill overnight.
Correct Approach
Copy only approved keys, review additions, and keep high-variety keys out of metrics entirely.
Problem
Services expect baggage to flow back up in responses, or expect a change made by a downstream service to be visible to the caller.
Why It’s Harmful
Standard propagation only moves in the direction of the request. Code that depends on upstream visibility will see empty or stale values and behave unpredictably.
Correct Approach
Return data in the response body or response fields, and use baggage only for downstream communication.
16Monitoring and Operating Baggage in Production
Because failures are silent, you need to look for them on purpose.
A baggage setup that worked on the day it was launched can drift over months. New services arrive with the wrong propagator settings, a platform team changes a proxy rule, or another team adds three new entries and doubles the header size. Treat baggage like any other shared contract and give it basic monitoring.
Measure header size
Track the size of the baggage header at the gateway and at a few internal points, and alert when it grows beyond a budget your team agrees on.
Count dropped or rejected requests
Watch for sudden increases in rejected requests at gateways and proxies after a change, because oversized headers show up there first as odd client errors.
Sample real requests for presence
Periodically check that a known baggage entry set at the edge arrives at a few deep services. A simple synthetic request with a harmless marker entry can do this automatically.
Review the key catalogue
Keep a short list of approved baggage names and review any new name that appears in traffic. Unknown keys are a sign that someone is using baggage in a way nobody agreed to.
Audit what leaves your network
Inspect outbound traffic from a sample of services to external hosts and confirm that no internal baggage header is being sent to third parties.
| What to watch | Why it matters | Warning sign |
|---|---|---|
| Baggage header size | Header budgets are shared with cookies and tokens | Steady growth or sudden jumps |
| Presence at deep services | Detects silent breaks in the chain | Marker entry missing downstream |
| Unknown baggage keys | Reveals undocumented use | New names appearing in traffic |
| Outbound headers to third parties | Prevents leakage of internal labels | Baggage header seen on external calls |
| Cardinality of copied labels | Controls telemetry cost | Fast growth in distinct label values |
Name an owner for the baggage contract, usually a platform or observability team. Without an owner, nobody notices when the contract drifts, and nobody has the authority to say no to a risky new key.
17Testing and Troubleshooting Propagation
A layered approach finds problems earlier and cheaper.
Since baggage failures are silent, a deliberate testing plan pays for itself. The idea is to prove the chain one layer at a time, starting from the smallest piece and growing towards the real environment. Each layer catches a different kind of mistake, and skipping a layer means the mistake will surface later in a more confusing form.
Inside one service
Verify that an entry set at the start of a handler is still readable in deeper functions, including work that moves to other threads or async tasks.
Across one hop
Start two small services in a test setup, set an entry in the first, call the second, and confirm the entry arrives with the same value.
Across the real path
In a staging environment that includes real gateways, meshes, and load balancers, send a marker entry from the edge and check that it reaches the deepest service.
In production, continuously
Run a harmless synthetic request with a marker entry on a schedule, and alert when the marker fails to arrive.
A calm way to troubleshoot
When an entry goes missing, resist the urge to guess. Walk the path in order, exactly as Figure 3 laid it out. Find the last place where the entry is definitely present and the first place where it is definitely absent. The problem lives in between those two points, and that narrow stretch usually contains only one library, one proxy, or one configuration file to inspect.
Confirm the entry is set
Read the baggage at the point where your code sets it. If it is not there, the problem is local and has nothing to do with the network.
Confirm it leaves the process
Capture the outgoing request and check for the baggage header. If it is missing, look at the propagator list and the client instrumentation.
Confirm it survives the middle
Compare the header at the sender with the header at the receiver. If it changed or vanished, inspect every intermediary in between.
Confirm it is extracted
If the header arrived but the entry is not readable, check the receiver’s propagator list, server instrumentation, and the format of the value.
Troubleshooting a lost parcel works the same way. You do not search every building in the country. You find the last depot that scanned it and the first depot that did not, and then you look in the one place between them.
18Frequently Asked Questions
Short answers to the questions people ask most often.
The standard configuration lists both trace context and baggage propagators by default, so with a default SDK and instrumented libraries it usually works without extra effort. If you override the propagator list, you must make sure baggage is still included on every service.
No. Baggage travels between services with the request, not to the telemetry backend. The collector does not receive it as part of normal telemetry. To see baggage in your backend, you must copy chosen entries onto spans or logs as attributes.
No. Baggage flows with the request direction only. A service can change its own copy, and those changes flow to its own downstream calls, but the callers above it never see them through baggage.
Not strictly. Baggage uses its own header and its own part of the context, so it can work without tracing, as long as the baggage propagator is configured. In practice most teams use both together.
No. The sampling decision belongs to the trace context. Baggage keeps travelling even when the request is not sampled, which is why it is dependable for application behaviour.
The specification describes minimum supported limits of 180 pairs, 4096 bytes per pair, and 8192 bytes in total, but practical systems should stay far below that. A handful of short entries is a healthy target, and many teams set an even tighter internal budget.
Baggage itself is plain text in a header. It is protected on the wire only if the connection is encrypted, and it is readable by every service that handles the request. Never put secrets or personal data in it.
The context that carries baggage follows the unit of work only where the runtime or a wrapper makes it. When work moves to a different thread or a scheduled task, you may need a context-aware executor or an explicit step that copies the context across.
No. Anyone who can send a request can add a baggage header, so it cannot prove who someone is or what they may do. Use verified credentials and server-side checks for permissions, and use baggage only for labels and hints.
Baggage travels to other services so their code can read it during the request. A span attribute is recorded on telemetry and sent to the backend for later analysis. They only meet if you deliberately copy baggage onto a span.
19Summary and Key Takeaways
Baggage lets a request carry a small set of labels from the first service to the last, without changing a single business API. Inside a process, the labels live in the context. At a boundary, a propagator injects them into a carrier such as an HTTP header, and on the other side another propagator extracts them into a fresh context. The same cycle repeats at every hop, so the labels follow the request through the whole chain, growing and changing as services add to them, always flowing in the direction of the request.
The power of baggage is also its danger. Because it rides on every call, it costs bytes at every hop, and because it is plain text that any caller can set, it must never carry secrets, never grant permissions, and never be trusted blindly at the edge. Used with discipline, a few tiny, well-documented labels, sanitised at trust boundaries and copied into telemetry through allow lists, it becomes one of the most practical tools for understanding and steering a distributed system. Like the festival wristband, it works best when it is small, simple, and means the same thing to everyone who reads it.
Key Takeaways
- Baggage is request-scoped labels — a small set of name and value pairs that belongs to the request and is readable by every service it touches.
- Propagation is inject then extract — a propagator writes baggage into the outgoing carrier, and the receiving side reads it back into a new context, using the W3C baggage header on HTTP.
- It flows downstream only — services pass baggage along automatically and may add to it, but changes never travel back up to callers on their own.
- Configuration must match on both ends — baggage must be in the propagator list of every service in the chain, and every boundary must be instrumented, or the chain breaks silently.
- Baggage is not telemetry — it does not reach your backend unless you copy approved entries onto spans or logs, and high-variety values should stay out of metrics.
- Keep it small — the cost is paid on every hop of every request, so a handful of short entries is far better than approaching the specification limits.
- Treat it as public — never put secrets, personal data, or permissions in baggage, sanitise it at trust boundaries, and do not let it leak to third parties.