How do I set up Elastic Node APM distributed tracing to work with Kafka and multiple Node services?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Elastic APM can connect traces across multiple Node services, but Kafka requires one extra step: you must propagate the trace context in message headers yourself. If you only install the APM agent and rely on HTTP auto-instrumentation, the Kafka handoff will break the trace.
The working setup is straightforward once you split it into two responsibilities: start the agent early in every service, then copy traceparent from producer to consumer.
Start the Agent Before Other Modules
Every Node service should start elastic-apm-node before loading Express, KafkaJS, database clients, or anything else the agent might instrument:
Then import it first:
Use a unique serviceName for each process, such as checkout-api, orders-consumer, or inventory-service. If several services share the same name, Kibana groups them together and the trace becomes harder to read.
Producer: Put traceparent in Kafka Headers
If a service is already inside a request transaction, Elastic exposes the current trace context through apm.currentTraceparent. Put that value into the Kafka headers when you send the message:
If this code runs outside any active transaction, start one explicitly before producing the message. Otherwise the send span will exist, but it will not belong to a useful distributed trace.
Consumer: Start a Child Transaction
On the consumer side, read the Kafka header and start a transaction with childOf:
Once that transaction is active, downstream HTTP calls from processOrder() can continue the same trace automatically if the receiving service also uses the agent correctly.
What the Full Trace Looks Like
A typical end-to-end flow is:
- '
api-gatewayreceives an HTTP request and starts an incoming transaction automatically' - '
api-gatewaypublishes a Kafka message with the currenttraceparent' - '
orders-consumerstarts a child transaction from that header' - '
orders-consumercalls another Node service over HTTP, and that HTTP hop continues the trace automatically'
When this is working, Kibana shows one distributed trace instead of isolated transactions.
Common Pitfalls
The most common mistake is starting the APM agent too late. If Express or KafkaJS loads before elastic-apm-node, instrumentation can be incomplete.
Another common problem is expecting Kafka context propagation to happen automatically. HTTP is mostly automatic; Kafka headers are not. You must write and read traceparent yourself.
Older examples sometimes use elastic-apm-traceparent. Current guidance is centered on the standard traceparent header, so mixed old and new services need extra care.
Finally, always end spans and transactions. Missing span.end() or transaction.end() leaves incomplete timing data and confusing traces in Kibana.
Summary
- Start the Elastic APM agent before other modules in every Node service.
- Give each service a unique
serviceName. - Put
apm.currentTraceparentinto Kafka message headers on the producer. - Use the received header as
childOfwhen starting the consumer transaction. - If the trace breaks in Kibana, check agent startup order, header propagation, and transaction lifecycle first.

