Skip to main content
nginx writes plain-text logs by default: a combined access log and a separate error log. This page tails both and ships them to Rootprint over OTLP with either the OpenTelemetry Collector or Vector. Records land in otel-logs-v0_9 tagged service.name: nginx, with access-log fields mapped to OpenTelemetry HTTP semantic conventions so they arrive as queryable attributes.

Prerequisites

  • A running Rootprint instance and its base URL — you’ll substitute it for <your-rootprint>.
  • A host running nginx that writes access and error logs to files. Stock nginx logs to /var/log/nginx/ on Debian/Ubuntu; the paths below assume those defaults.
  • A log agent that can read those files — the OpenTelemetry Collector or Vector.
  • An ingest API key scoped to your target index. See API keys.
This config assumes the agent runs where it can read /var/log/nginx.
1

Confirm nginx is logging to file

Stock nginx writes combined-format access logs to /var/log/nginx/access.log and error logs to /var/log/nginx/error.log. These defaults are what the config below expects. No nginx.conf changes are required.
2

Configure the Collector

Replace <your-rootprint> with your Rootprint base URL and <your-ingest-token> with the key from above.
3

Grant read access to /var/log/nginx

nginx’s Debian/Ubuntu package owns /var/log/nginx/ with the adm group as the read-side convention. Add the user the Collector runs as to that adm group (the Debian/RPM Collector package creates otelcol-contrib).
4

Start the Collector

Start (or restart) the Collector so it loads the config. Startup logs should show no config-parse or exporter errors.
5

Send a test request

6

Verify in Rootprint

Open Search, pick otel-logs-v0_9, and query service_name:nginx. Records typically appear within 5–10 seconds.

What you get in Rootprint

Both agents parse nginx’s stock combined access log and error log format. Access-log fields are mapped to OpenTelemetry HTTP semantic conventions; error-log fields go to process and connection attributes. Both agents write to otel-logs-v0_9 with service.name: nginx. The nginx.format discriminator ("access" or "error") lets one config handle both files: error-log entries get process.pid, thread.id, and nginx.connection_id; access-log entries get the full HTTP attribute set. timeUnixNano is derived from the timestamp in each log line. If parsing fails, the agent’s read time is used as a fallback. With the OpenTelemetry Collector, error-log context fields (client.address on error lines, server.address, nginx.server, and nginx.upstream) are not broken out into attributes (they remain in the record body); the Vector path extracts them via parse_nginx_log.

Useful searches

Run these in the Rootprint search box against otel-logs-v0_9. Every 5xx response nginx returned, across all sites:
All 4xx responses for one URL prefix, useful for spotting bad clients hammering one endpoint:
Activity from a single source IP, pivot from a suspect address back to everything it touched:
Failed POSTs only:
Error-log entries only (skip access logs):
Upstream failures (the things that page someone). nginx.upstream is populated by the Vector path only (the OTEL Collector keeps error-log context in the record body):

Troubleshooting

  • Records arrive but attributes.http.* are missing: the entry came from error.log (no access fields), by design. Filter access-log entries with attributes.nginx.format:access.
  • body looks like a raw nginx line instead of a structured message: the access log is using a non-default log_format directive in nginx.conf. The parser expects nginx’s stock combined format. Either remove the custom log_format or extend the config to handle the alternate shape.
  • severity_text is always INFO even for 5xx responses: by design. nginx’s combined access format has no severity field; both agents trust the source. Filter on attributes.http.response.status_code:>=500 to surface server errors.
  • Ingest transport errors (401, 403, 413, 415): see OTLP reference → Response codes.