OpenTelemetry Error Guide: 'error reading from server: EOF' — Fix OTLP Dropped Streams
Fix 'rpc error: code = Unavailable desc = error reading from server: EOF': the server or proxy closed the OTLP stream. Tune keepalive.
- #opentelemetry
- #observability
- #troubleshooting
- #errors
Stuck on this OpenTelemetry error? Get the free incident triage checklist
A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.
Overview
This error appears when the server or an intermediary closes the OTLP connection while the exporter is still reading the response. The client reaches end-of-file where it expected more data, and gRPC reports it as Unavailable:
rpc error: code = Unavailable desc = error reading from server: EOF
A closely related transport variant reads:
rpc error: code = Unavailable desc = error reading from server: read tcp 10.0.4.12:52788->10.0.7.9:4317: read: connection reset by peer
An unexpected EOF means the far end hung up mid-stream — an idle timeout fired, the connection was recycled, a message exceeded a limit and the server closed the socket, or a load balancer dropped the backend. Because gRPC classifies it as Unavailable, it is retryable: the in-flight batch is retried if a retry queue is configured, otherwise dropped.
Symptoms
error reading from server: EOFappears intermittently, often after idle periods or during backend rollouts.- Errors cluster on long-lived connections that have been open for minutes.
- A load balancer, proxy, or service-mesh sidecar sits between the exporter and the Collector.
- Large batches fail with EOF while small ones succeed (server closing on size).
otelcol_exporter_send_failed_spansblips then recovers as retries land on a healthy connection.
Common Root Causes
- Idle-connection timeout — an LB/NAT/proxy silently closes an idle keepalive connection; the next export reads EOF on a dead socket.
- max_connection_age recycle — the server deliberately closes long-lived HTTP/2 connections and the client sees EOF on the in-flight stream.
- Message too large closing the connection — a batch over the server’s
max_recv_msg_sizecan cause the server to close the socket instead of returning a cleanResourceExhausted. - Backend/gateway rollout — a Collector replica terminates mid-request and the stream ends in EOF.
- Load balancer draining — the LB removes a backend from rotation and cuts the connection.
- Mismatched keepalive — the client never pings, so a proxy considers the connection idle and closes it between exports.
Diagnostic Workflow
Determine whether EOF follows idle gaps (keepalive/idle-timeout) or large batches (message-size), and what sits in the path:
env | grep OTEL_EXPORTER_OTLP
journalctl -u otelcol-contrib --since '15 min ago' | grep -i 'EOF\|reading from server\|too large\|keepalive'
# Is a mesh sidecar in the pod?
kubectl get pod -l app=my-app -o jsonpath='{.items[0].spec.containers[*].name}'
Keep the connection warm with client keepalive and ensure a retry queue absorbs the occasional recycle:
exporters:
otlp:
endpoint: otel-gateway:4317
tls:
insecure: false
keepalive:
time: 30s # ping every 30s so idle proxies don't close us
timeout: 10s
permit_without_stream: true
retry_on_failure:
enabled: true
initial_interval: 5s
max_elapsed_time: 300s
sending_queue:
enabled: true
num_consumers: 4
queue_size: 5000
On the receiving Collector, permit those pings, recycle connections gracefully, and raise the accepted message size so large batches aren’t closed:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
max_recv_msg_size_mib: 16
keepalive:
server_parameters:
max_connection_age: 300s
max_connection_age_grace: 30s
enforcement_policy:
min_time: 20s # must be <= client keepalive time
permit_without_stream: true
If the EOF only appears on large exports, cap batch size well under the server limit:
processors:
batch:
send_batch_size: 512
send_batch_max_size: 2048 # keep well under max_recv_msg_size
Then validate and confirm the receiver bound its port:
otelcol-contrib validate --config /etc/otelcol-contrib/config.yaml
Example Root Cause Analysis
Application pods exported traces through an L7 load balancer to a gateway Collector. Traffic was bursty: quiet for a minute, then a spike. During the quiet windows the exporter held an idle HTTP/2 connection open but never pinged it, so the load balancer’s 60-second idle timeout closed the backend leg. The next export after each lull read rpc error: code = Unavailable desc = error reading from server: EOF, and because retry_on_failure had been left disabled, that first batch after every idle window was dropped.
The fix had two parts. First, the exporter gained keepalive.time: 30s with permit_without_stream: true so the connection was pinged during idle windows and the LB no longer considered it stale. Second, retry_on_failure and a sending_queue were re-enabled so the rare EOF from a genuine connection recycle became a resumed export. After the change, the post-idle EOFs disappeared and dropped spans went to zero.
Prevention Best Practices
- Enable client keepalive (
time≥ the server’senforcement_policy.min_time) so idle proxies and LBs don’t close the connection between bursts. - Always keep
retry_on_failureand a boundedsending_queueon so a mid-stream EOF is retried, not dropped. - Set the server’s
max_connection_agewith a grace window so long-lived connections are recycled cleanly instead of abruptly. - Align proxy/LB idle timeouts with the OTLP route and keep them longer than your keepalive interval.
- Cap
send_batch_max_sizeunder the receiver’smax_recv_msg_size_mibso oversized batches don’t cause the server to close the socket. - Alert only on sustained EOF/
Unavailable; occasional recycles are normal and should be absorbed by retries.
Quick Command Reference
# Find EOF / mid-stream closes in Collector logs
journalctl -u otelcol-contrib -f | grep -i 'EOF\|reading from server'
# Confirm the gateway is up and speaking gRPC
grpcurl -plaintext otel-gateway:4317 list
# Validate keepalive/message-size config after editing
otelcol-contrib validate --config /etc/otelcol-contrib/config.yaml
# Watch failures recover as retries land
curl -s http://localhost:8888/metrics | grep -E 'exporter_send_failed|exporter_sent'
Conclusion
error reading from server: EOF means the connection ended before the response did — the server, a proxy, or a load balancer hung up mid-stream, usually from an idle timeout, a connection recycle, or an oversized batch. Keep the link warm with client keepalive, let the server recycle connections gracefully with max_connection_age, cap batch sizes under the receiver limit, and lean on retry_on_failure so a rare mid-stream close resumes instead of dropping. Reserve alerts for sustained EOFs so normal connection churn stays invisible.
Related
- OpenTelemetry Error Guide: ‘connection reset by peer’ on OTLP export
- OpenTelemetry Error Guide: ‘context deadline exceeded’
- OpenTelemetry Error Guide: ‘connection refused’ to the Collector
Fixed it? Get 500 OpenTelemetry & 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.
Did this fix your issue?
Trending errors this week
The error guides other engineers are actually reading right now.
- 1mount: wrong fs type, bad option, bad superblock
- 2Docker 'failed to set up container networking': Fix the Bridge and IP Pool
- 3Docker 'failed to create shim task': How to Fix the containerd Runtime Error
- 4modprobe: FATAL: Module not found
- 5mount: wrong fs type, bad option, bad superblock
- 6Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
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.