OpenTelemetry Error Guide: 'server gave HTTP response to HTTPS client' — Fix OTLP TLS Mismatch
Fix 'http: server gave HTTP response to HTTPS client' on OTLP: the exporter uses TLS but the Collector is plaintext. Fix the scheme or tls.insecure.
- #opentelemetry
- #observability
- #troubleshooting
- #errors
Fixing errors like this? Get 500 free DevOps AI prompts
500 copy-paste AI prompts for the stack you actually run — one PDF, free.
Overview
This error appears when an OTLP client speaks TLS (an https:// endpoint or a gRPC exporter with TLS enabled) but the Collector is listening in plaintext. The client sends a TLS ClientHello, the server answers with a plain HTTP response, and the client rejects it. It surfaces in SDK stderr and in the Collector’s own exporter logs:
traces export: Post "https://otel-collector.example.com:4318/v1/traces": http: server gave HTTP response to HTTPS client
The gRPC exporter variant wraps the same cause in a transport error:
error exporterhelper/queue_sender.go:128 Exporting failed. Dropping data. {"kind": "exporter", "data_type": "traces", "name": "otlp", "error": "rpc error: code = Unavailable desc = connection error: desc = \"transport: authentication handshake failed: http: server gave HTTP response to HTTPS client\""}
http: server gave HTTP response to HTTPS client means the client expected TLS but the endpoint answered in cleartext — a scheme/port/tls.insecure mismatch, not a certificate problem.
Symptoms
- Every export fails the same way; the error is deterministic, never intermittent.
- The endpoint URL starts with
https://or the gRPC exporter has TLS enabled, yet the target port is plaintext. - Switching the endpoint to
http://(or settingtls.insecure: true) makes the error vanish. curl https://endpoint/v1/tracesfails whilecurl http://endpoint/v1/tracessucceeds.- The Collector’s OTLP receiver has no
tls:block, so it serves plaintext. - The mistake often follows copy-pasting a vendor’s
https://example onto a local plaintext Collector.
Common Root Causes
- Endpoint scheme is https but the Collector is plaintext — the receiver has no
tls:block, so it never speaks TLS on that port. - gRPC exporter TLS left enabled —
tls.insecureis unset (defaults to secure) while pointing at a plaintext gRPC receiver. - Wrong port — the client targets the plaintext port (
4317/4318) but assumes it is TLS-terminated. - TLS terminated elsewhere — a load balancer or sidecar is supposed to terminate TLS but is bypassed, so the client hits the raw plaintext Collector.
- Vendor sample copied verbatim — an
https://SaaS endpoint config was reused against a local test Collector that only serves cleartext. - Mixed HTTP/gRPC assumptions — the exporter protocol or port does not match how the receiver is actually configured.
Diagnostic Workflow
Confirm the exact endpoint, protocol, and whether the client is in secure mode:
echo "$OTEL_EXPORTER_OTLP_ENDPOINT" # https://... means the client expects TLS
echo "$OTEL_EXPORTER_OTLP_PROTOCOL" # grpc | http/protobuf
echo "$OTEL_EXPORTER_OTLP_INSECURE" # true = plaintext for gRPC SDK exporters
Probe the endpoint both ways. If the plaintext request works and the TLS one fails, the server is plaintext:
# Plaintext succeeds -> the endpoint is NOT serving TLS
curl -v -m 5 http://otel-collector.example.com:4318/v1/traces -X POST -H 'Content-Type: application/json' -d '{}'
# TLS fails with the handshake/HTTP-response error
curl -v -m 5 https://otel-collector.example.com:4318/v1/traces -X POST -H 'Content-Type: application/json' -d '{}'
You then have two correct fixes. Option A — the endpoint really is plaintext (local/dev): make the client match it. For the Collector’s otlp exporter, set tls.insecure and use an http:// endpoint:
exporters:
otlp:
endpoint: otel-collector.example.com:4317
tls:
insecure: true # plaintext gRPC; no TLS attempted
otlphttp:
endpoint: http://otel-collector.example.com:4318 # http scheme, not https
Option B — the endpoint should be TLS (production): enable TLS on the receiver instead of downgrading the client:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
tls:
cert_file: /etc/otelcol/certs/server.crt
key_file: /etc/otelcol/certs/server.key
http:
endpoint: 0.0.0.0:4318
tls:
cert_file: /etc/otelcol/certs/server.crt
key_file: /etc/otelcol/certs/server.key
Validate and watch for the error clearing:
otelcol-contrib validate --config /etc/otelcol/config.yaml
journalctl -u otelcol-contrib --since '10 min ago' | grep -i 'https client\|tls\|export'
Example Root Cause Analysis
A developer copied a SaaS onboarding snippet that set OTEL_EXPORTER_OTLP_ENDPOINT=https://otel-collector.example.com:4318 and pointed it at a local Collector started from the default plaintext config. Every export failed with http: server gave HTTP response to HTTPS client, because the local receiver had no tls: block and answered the TLS ClientHello with a plain HTTP 200-style response.
The fix depended on intent. For the local dev loop, the endpoint was changed to http://otel-collector.example.com:4318 so the client stopped attempting TLS, and traces flowed immediately. For the staging gateway, the opposite fix was correct: a tls: block with cert_file/key_file was added to both the gRPC and HTTP receiver protocols so the endpoint genuinely served TLS, keeping the https:// scheme intact and encrypting telemetry on the wire.
Prevention Best Practices
- Keep the endpoint scheme and the receiver configuration in lockstep:
https://(or gRPC secure) requires atls:block on the receiver. - For local/dev Collectors, set
tls.insecure: trueexplicitly so intent is obvious and no accidental TLS is attempted. - Never copy a vendor’s
https://endpoint onto a plaintext Collector without also configuring TLS or switching the scheme. - Standardize ports: reserve TLS listeners on dedicated ports so
4317/4318are unambiguous across environments. - Terminate TLS at a well-known layer (Collector, sidecar, or LB) and document which one, so clients know whether to use TLS.
- Add a smoke test that
curls the real endpoint with the intended scheme in CI before shipping exporter config.
Quick Command Reference
# Show what the client expects
env | grep OTEL_EXPORTER_OTLP
# Prove the endpoint is plaintext (http works, https fails)
curl -v -m 5 http://otel-collector.example.com:4318/v1/traces -X POST -d '{}'
curl -v -m 5 https://otel-collector.example.com:4318/v1/traces -X POST -d '{}'
# Validate config after fixing scheme or tls block
otelcol-contrib validate --config /etc/otelcol/config.yaml
# Watch the error clear
journalctl -u otelcol-contrib -f | grep -i 'https client\|export'
Conclusion
http: server gave HTTP response to HTTPS client is a plaintext-versus-TLS mismatch: the exporter attempts TLS but the Collector endpoint answers in cleartext. Decide what the endpoint should be. For local development, point the client at http:// or set tls.insecure: true. For production, keep https:// and add a tls: block with cert_file/key_file to the receiver so the endpoint actually terminates TLS. Aligning the scheme, port, and receiver configuration makes the error disappear and keeps telemetry encrypted where it matters.
Related
- OpenTelemetry Error Guide: ‘x509: certificate signed by unknown authority’
- OpenTelemetry Error Guide: ‘connection refused’ to the Collector
- OpenTelemetry Error Guide: ‘rpc error: code = Unimplemented’
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.