Two Varnish counters track the worst outcome a cache can produce: a client gets nothing. MAIN.sess_dropped counts HTTP/1 sessions dropped because the worker thread queue was full. MAIN.req_dropped counts HTTP/2 streams and other request types dropped for the same reason. Both mean Varnish refused to serve a client because no thread was available and the bounded queue was at its limit.
The distinction matters because HTTP/1 and HTTP/2 multiplex differently. An HTTP/1 client occupies a connection. An HTTP/2 client multiplexes many concurrent streams over a single connection. When Varnish runs out of threads, the rejection mechanism differs by protocol, and so does the counter that increments. Teams that monitor only sess_dropped can see zero drops while HTTP/2 streams are silently refused.
What these counters mean
| Counter | What it counts | Protocol | Version scope |
|---|---|---|---|
MAIN.sess_dropped | HTTP/1 sessions (connections) dropped because the queue was too long | HTTP/1 | Standard in modern Varnish |
MAIN.req_dropped | HTTP/2 streams and other request types dropped because the queue was too long | HTTP/2 and other | V7+ |
Both are cumulative counters. They only increase until the child process restarts, at which point all MAIN.* counters reset to zero. Monitor the rate of increase (delta per second), not the absolute value. A counter reading 50,000 tells you nothing without knowing whether those drops accumulated over a year or over the last 30 seconds.
Both counters increment when the session queue reaches thread_queue_limit. New arrivals are dropped instead of queued. The queue depth is visible as MAIN.thread_queue_len, a gauge that updates once per second.
Check both counters in one command:
# Check both drop counters simultaneously
varnishstat -1 -f MAIN.sess_dropped -f MAIN.req_dropped
HTTP/1 vs HTTP/2: why there are two counters
Varnish’s concurrency model is thread-per-request with a bounded pool. When a client request arrives and no worker thread is available, the request enters a bounded queue. When that queue hits thread_queue_limit, the request is dropped.
The protocol determines what “dropped” means:
- HTTP/1: Each connection carries one request at a time. Dropping a session means closing the TCP connection. The client sees a connection reset or refused. This increments
MAIN.sess_dropped. - HTTP/2: A single TCP connection multiplexes multiple concurrent streams. Varnish can refuse an individual stream without closing the connection. The client sees a stream reset (RST_STREAM) while the connection itself remains open. This increments
MAIN.req_dropped.
A drop at the connection level (HTTP/1) and a drop at the stream level (HTTP/2) are fundamentally different events, even though the root cause (no thread available, queue full) is the same.
flowchart TD
A[Client request arrives] --> B{Worker thread available?}
B -- Yes --> C[Request processed]
B -- No --> D[Enters session queue
thread_queue_len]
D --> E{Queue at thread_queue_limit?}
E -- No --> F[Waits for worker]
F --> C
E -- Yes --> G{Protocol}
G -- HTTP/1 --> H["sess_dropped increments
connection reset"]
G -- HTTP/2 --> I["req_dropped increments
stream reset"]The legacy sess_drop naming trap
If you have been running Varnish for years, you may know the counter as MAIN.sess_drop (no trailing “ped”). This is the older spelling. It was marked deprecated in Varnish Cache 6.2.0 and was subsequently removed from the codebase. The current counter is MAIN.sess_dropped.
The trap works in two directions:
Monitoring a dead counter. If your dashboards or alerts reference
MAIN.sess_drop, they show zero forever. The counter either does not exist (removed) or is never incremented (deprecated but present). Meanwhile, HTTP/1 connections are being dropped and counted undersess_dropped.Assuming the name change was cosmetic. The rename from
sess_droptosess_droppedcoincided with the introduction ofreq_droppedfor HTTP/2. The pair (sess_dropped,req_dropped) covers both protocols. Monitoringsess_droppedalone still misses HTTP/2 drops.
If you are unsure which counter name your Varnish version exposes:
# List available session/request drop counters
varnishstat -1 | grep -E 'sess_dropp|req_dropp|sess_drop'
What a drop means for the client
Each drop is a hard failure. The client receives nothing: no cached response, no 503 error page, no synthetic response from VCL. The connection or stream is reset.
This is different from a 503 response. A 503 means Varnish accepted the request, processed it through VCL, attempted a backend fetch (or decided to synthesize an error), and returned a response. A drop means Varnish never got that far. The request never entered VCL processing because no thread was available to handle it.
Dropped requests do not appear in varnishlog or varnishncsa output. There is no transaction log entry because the request was never assigned to a worker thread. The counters are the only detection mechanism. If you rely solely on log-based monitoring, you have a blind spot for exactly the failure mode that represents the most severe user impact.
The monitoring mistake: watching only one
With HTTP/2, overload manifests as req_dropped (stream drops), not sess_dropped (connection drops). Teams monitoring only sess_dropped miss HTTP/2 traffic loss entirely. This is common because environments that originally deployed monitoring for HTTP/1-only traffic still have those dashboards. When TLS termination was added in front of Varnish (via Hitch, HAProxy, or similar) with ALPN negotiation for HTTP/2, the traffic profile changed but the monitoring did not.
The fix is to monitor both counters as a combined drop rate:
# Combined drop rate (take two readings, compute delta)
varnishstat -1 -f MAIN.sess_dropped -f MAIN.req_dropped
Alert on the combined rate (sess_dropped rate + req_dropped rate) sustained above zero. Any nonzero sustained drop rate with active traffic means Varnish is refusing clients.
For the full set of common Varnish monitoring gaps, see the Varnish monitoring checklist.
Reading the rate, not the cumulative counter
Both sess_dropped and req_dropped are monotonically increasing counters that reset only on child restart. A single snapshot of the raw value is useless for alerting. You need the rate of change.
To compute the rate manually, take two readings and divide by the interval:
# Manual rate check: two readings, 10 seconds apart
T1=$(varnishstat -1 -f MAIN.sess_dropped -f MAIN.req_dropped)
sleep 10
T2=$(varnishstat -1 -f MAIN.sess_dropped -f MAIN.req_dropped)
echo "$T1"; echo "$T2"
# Subtract per-counter values, divide each delta by 10 for drops/sec
For production alerting, use a monitoring system that computes per-second rates automatically. The key conditions for a page-worthy alert:
- Combined drop rate (
sess_dropped+req_dropped) sustained greater than 0 for more than 120 seconds. MAIN.uptimegreater than 300 seconds (excludes the warmup window after restart, where brief drops can occur as the thread pool ramps fromthread_pool_min).MAIN.client_reqgreater than 0 (confirms live traffic is present, preventing false fires on idle nodes).MAIN.thread_queue_lengreater than 0 (confirms queue saturation as the cause).
Zero is the only acceptable sustained value. Any nonzero combined rate, sustained past the warmup window, indicates a fault that is actively refusing client traffic.
Correlating with thread pool saturation
Drops are the terminal symptom. Thread pool saturation is the cause. When you see drops incrementing, the following chain has already occurred:
- All worker threads are busy (typically blocked on slow backend responses).
- New requests enter the queue (
thread_queue_lenrises above zero). - The queue reaches
thread_queue_limit. - New arrivals are dropped.
The diagnostic signals to correlate:
| Signal | What it tells you | Why it matters |
|---|---|---|
MAIN.thread_queue_len | Current queue depth (gauge, updates once per second) | Leading indicator. When this rises, drops follow. |
MAIN.threads | Current total worker threads | If equal to thread_pool_max * thread_pools, the pool is at capacity. |
MAIN.threads_limited | Count of times thread creation hit thread_pool_max | Incrementing means Varnish wanted more threads but the config prevented it. |
MAIN.threads_failed | Count of times the OS refused thread creation | Incrementing means a system-level limit (ulimit, memory, cgroup) is blocking thread creation. This is worse than threads_limited. |
MAIN.client_req | Client request rate | Confirms traffic is present. Drops on an idle server are suspicious. |
# Full thread pool saturation snapshot
varnishstat -1 -f MAIN.threads -f MAIN.thread_queue_len \
-f MAIN.threads_limited -f MAIN.threads_failed -f MAIN.pools \
-f MAIN.sess_dropped -f MAIN.req_dropped
If threads_limited is incrementing alongside drops, the pool is too small for the traffic volume. If threads_failed is incrementing, the OS is refusing to create threads (check ulimit -u, available memory, and cgroup limits). Both require different remediation: the first needs a thread_pool_max increase, the second needs OS-level configuration changes.
For a deeper treatment of thread pool exhaustion diagnosis and remediation, see the thread pool exhaustion guide.
Distinguishing capacity exhaustion from attack
Not all drops mean your capacity is too low. A sudden spike in drops without a corresponding increase in legitimate traffic can indicate an attack.
The CVE-2024-30156 “Broke Window Attack” targets HTTP/2 flow control in Varnish Cache. Affected versions include releases before 7.3.2, 7.4.x before 7.4.3, and certain LTS and Enterprise versions. An attacker exhausts HTTP/2 connection flow control credits, causing denial of service. Detection includes spikes in sess_dropped or req_dropped without matching legitimate traffic increase.
To distinguish capacity exhaustion from attack:
- Capacity exhaustion:
client_reqrate is elevated,thread_queue_lenis high, backend response times are elevated (slow backends holding threads).threads_limitedincrements steadily. The drop rate correlates with traffic volume. - Attack pattern: Drop rate spikes without proportional increase in
client_req. Backend response times may be normal. HTTP/2-specific abuse counters (MAIN.sc_rapid_reset,MAIN.sc_bankrupt) may also increment. The drop pattern may be bursty or sustained regardless of backend health.
Additional HTTP/2 security signals to check:
# HTTP/2 protocol abuse counters
varnishstat -1 -f MAIN.sc_rapid_reset -f MAIN.sc_bankrupt -f MAIN.req_reset
sc_rapid_reset detects the CVE-2023-44487 Rapid Reset DDoS pattern. sc_bankrupt indicates a session exceeded its credit limit. These are V7+ counters.
Cold start and warmup considerations
During the first few minutes after a child process restart, brief session drops can occur. The thread pool ramps from thread_pool_min, and if a traffic spike hits during warmup with a conservative thread_pool_add_delay, temporary drops are possible. These are transient and self-resolve.
This is why the alerting threshold includes MAIN.uptime > 300. The five-minute window excludes the warmup period from alerting. The MGT.* counters (management process) do not reset on child restart, but all MAIN.* counters do. Rate calculations that span a child restart will produce incorrect values for that interval.
After restart, expect the cache hit rate to start at zero and climb as the cache warms. Drops during warmup with an empty cache are particularly likely because every request is a cache miss, every miss requires a backend fetch, and each fetch holds a thread longer than a cache hit would.
Correlating drops in Netdata
Netdata collects these counters at per-second resolution. For drop diagnosis, the useful correlations are:
- Protocol-specific drop rates.
sess_droppedandreq_droppedrate charts show whether losses are concentrated in HTTP/1 or HTTP/2. This is the signal that catches the “monitoring only sess_dropped” blind spot. - Queue depth timeline.
thread_queue_lencharts alongside drop rates, making the causal chain visible. Per-second collection catches sub-second spikes that the Varnish gauge itself (which updates once per second) can smooth over. - Thread pool limits vs OS refusals.
threads_limitedandthreads_failedalongside drops distinguish a config ceiling from an OS-level limit without running separatevarnishstatinvocations. - HTTP/2 abuse counters.
sc_rapid_resetandsc_bankruptappear alongside drop rates when attack patterns are involved.






