For the complete documentation index, see llms.txt. Markdown versions are available by appending .md to documentation URLs.

NGINX OpenTelemetry Instrumentation - Trace Requests

SigNoz Cloud - This page applies to SigNoz Cloud editions.
Self-Host - This page applies to self-hosted SigNoz editions.

This guide shows you how to trace requests through NGINX with OpenTelemetry and send the spans to SigNoz. You load the native NGINX OpenTelemetry module (ngx_otel_module), point it at SigNoz, and NGINX creates a server span for every request it handles.

Prerequisites

  • Linux on x86-64 or ARM64
  • NGINX from the nginx.org packages or the official nginx Docker image. The module package matches these builds. Ubuntu and Debian repositories do not ship it.
  • An instance of SigNoz (either Cloud or Self-Hosted)

Send traces to SigNoz

Step 1. Install the OpenTelemetry module

Set up the nginx.org package repository for your distribution with the official NGINX instructions. Then install NGINX and the module:

sudo apt update
sudo apt install nginx nginx-module-otel

The package installs ngx_otel_module.so into /etc/nginx/modules/.

Step 2. Load the module

Add this line at the top of /etc/nginx/nginx.conf, outside every block:

/etc/nginx/nginx.conf
load_module modules/ngx_otel_module.so;

Step 3. Configure the exporter

Create /etc/nginx/conf.d/otel.conf. The default nginx.conf includes conf.d/*.conf inside its http block, so these directives apply to every server:

/etc/nginx/conf.d/otel.conf
otel_exporter {
    endpoint https://ingest.<region>.signoz.cloud:443;
    header signoz-ingestion-key <your-ingestion-key>;
}
 
otel_service_name <service-name>;
otel_resource_attr service.version <service-version>;
otel_trace on;
otel_trace_context propagate;

Verify these values:

  • <region>: Your SigNoz Cloud region
  • <your-ingestion-key>: Your SigNoz ingestion key
  • <service-name>: A descriptive name for this NGINX instance (for example, edge-nginx)
  • <service-version> (optional): Your release version or git SHA (for example, 1.4.2). Delete the line if you do not need it.

Two directives control tracing:

  • otel_trace on creates a span for every request.
  • otel_trace_context propagate continues an incoming W3C traceparent header and passes the context to upstream servers. Instrumented backends then join the same trace.

Step 4. Restart NGINX

Check the configuration, then restart NGINX:

sudo nginx -t
sudo systemctl restart nginx

nginx -t should print syntax is ok and test is successful. Use restart rather than reload: a fresh nginx.org install leaves the service stopped, and reload fails on a stopped service.

Validate

  1. Send a few requests through NGINX, for example curl http://localhost/.
  2. Open SigNoz and navigate to Traces.
  3. Filter by the service.name you set with otel_service_name.
SigNoz Traces Explorer listing NGINX request spans filtered by service.name edge-nginx
NGINX request spans in the Traces Explorer, filtered by service name
  1. Click a span to see its attributes, such as http.method, http.target, and http.status_code.
SigNoz trace detail view showing an NGINX server span with its HTTP attributes
An NGINX server span with the attributes the module records

NGINX exports spans every 5 seconds, so wait a few seconds before you refresh.

Each span is named after the location block that matched the request, such as / or /api/orders. To set a different name, add otel_span_name inside a location block:

location /api/orders {
    otel_span_name "GET /api/orders";
    proxy_pass http://orders:8080;
}

NGINX marks spans for 5xx responses as errors. Spans for 4xx responses keep an unset status.

Troubleshooting

NGINX writes exporter errors to its error log. On a VM, check /var/log/nginx/error.log. In Docker and Kubernetes, run docker logs <container> or kubectl logs deployment/<deployment-name>.

Why does nginx -t report unknown directive "otel_exporter"?

NGINX has not loaded the module. Add load_module modules/ngx_otel_module.so; as the first line of nginx.conf, outside every block, then run nginx -t again. The error can name any otel_* directive.

Why does the log show OTel export failure: Invalid or missing key?

SigNoz rejected the ingestion key. Check the header signoz-ingestion-key value, and confirm that the key belongs to the region in your endpoint. In Docker and Kubernetes, confirm that the SIGNOZ_INGESTION_KEY environment variable reaches the container.

Why does the log show OTel export failure: Stream removed?

The endpoint contains a path, such as /v1/traces. The module exports over OTLP/gRPC, which takes only a host and port. Set the endpoint to https://ingest.<region>.signoz.cloud:443.

Why does the log show OTel export failure: failed to connect to all addresses?

The endpoint lacks the https:// prefix, so the module opens an unencrypted connection to a TLS port. Write the endpoint as https://ingest.<region>.signoz.cloud:443. For a Collector on the same host, check that it listens on port 4317.

Why does the service show as unknown_service:nginx?

The configuration has no otel_service_name directive. Add it to the http context, next to otel_exporter.

Why is the NGINX span in a separate trace from the backend?

By default, NGINX ignores trace headers. It starts a new trace for its own span and forwards the original traceparent header unchanged, so the backend joins the caller's trace instead. Add otel_trace_context propagate; so NGINX continues the incoming trace and passes its own span context upstream.

For more help with missing data, see Debug missing traces, logs, and metrics in SigNoz.

Setup OpenTelemetry Collector (Optional)

What is the OpenTelemetry Collector?

The Collector sits between NGINX and SigNoz. NGINX sends spans to the Collector, and the Collector forwards them to SigNoz.

Why use it?

  • Cleaning up data: Filter out noisy spans, or remove sensitive attributes before they leave your servers.
  • Adding context: Tag spans with host, Kubernetes, or cloud metadata.
  • One agent for all signals: The same Collector can also scrape NGINX metrics and tail NGINX logs.

To send spans through a Collector, point the exporter at the Collector's OTLP/gRPC receiver and remove the header line, because the Collector holds the ingestion key:

/etc/nginx/conf.d/otel.conf
otel_exporter {
    endpoint http://localhost:4317;
}

See Switch from direct export to Collector to set up the Collector, and the Collector configuration guide for its options.

Alternative: OpenTelemetry webserver module (Optional)

The OpenTelemetry project maintains a separate webserver module for NGINX. Besides the request span, it creates a span for each NGINX module that handles the request, such as ngx_http_rewrite_module. Use it only if you need those per-module spans.

This Dockerfile installs the webserver module on NGINX 1.26.0:

Dockerfile
FROM nginx:1.26.0
 
RUN apt-get update && apt-get install -y curl \
 && curl -fsSL https://github.com/open-telemetry/opentelemetry-cpp-contrib/releases/download/webserver%2Fv1.1.0/opentelemetry-webserver-sdk-x64-linux.tgz \
      | tar xz -C /opt \
 && cd /opt/opentelemetry-webserver-sdk && ./install.sh
 
ENV LD_LIBRARY_PATH=/opt/opentelemetry-webserver-sdk/sdk_lib/lib
RUN sed -i '1i load_module /opt/opentelemetry-webserver-sdk/WebServerModule/Nginx/1.26.0/ngx_http_opentelemetry_module.so;' /etc/nginx/nginx.conf
 
COPY opentelemetry_module.conf.template /etc/nginx/templates/opentelemetry_module.conf.template

Create opentelemetry_module.conf.template next to the Dockerfile:

opentelemetry_module.conf.template
NginxModuleEnabled ON;
NginxModuleOtelSpanExporter otlp;
NginxModuleOtelExporterEndpoint https://ingest.<region>.signoz.cloud:443;
NginxModuleOtelSslEnabled ON;
NginxModuleOtelSslCertificatePath /etc/ssl/certs/ca-certificates.crt;
NginxModuleOtelExporterOtlpHeaders signoz-ingestion-key=${SIGNOZ_INGESTION_KEY};
NginxModuleServiceName <service-name>;
NginxModuleServiceNamespace <service-namespace>;
NginxModuleServiceInstanceId <service-instance-id>;
NginxModuleResolveBackends ON;
NginxModuleTraceAsError ON;

Verify these values:

  • <region>: Your SigNoz Cloud region
  • <service-name>: A descriptive name for this NGINX instance (for example, edge-nginx)
  • <service-namespace>: A namespace for the service (for example, platform)
  • <service-instance-id>: A unique ID for this instance (for example, nginx-1)

Build and run it as shown in the Docker tab, passing SIGNOZ_INGESTION_KEY with -e.

Send NGINX metrics and logs

The OpenTelemetry module sends traces only. Use the OpenTelemetry Collector for the other signals:

  • Metrics: Enable the NGINX stub_status endpoint and scrape it with the Collector's nginx receiver. See Monitor NGINX metrics.
  • Logs: Tail the NGINX access and error logs with the Collector and parse them into structured fields. See NGINX logs.

Next steps

Get Help

If you need help with the steps in this topic, please reach out to us on SigNoz Community Slack. If you are a SigNoz Cloud user, please use in product chat support located at the bottom right corner of your SigNoz instance or contact us at cloud-support@signoz.io.

Is this page helpful

Last updated—October 06, 2026

Edit on GitHub