Ceph RGW failed requests: aborted request rate and what it actually counts
ceph_rgw_failed_req is one of the most commonly misread RADOS Gateway metrics. Operators see “failed” in the name and assume it counts HTTP 4xx and 5xx responses. It does not. It counts requests where the client connection was aborted before the response completed. That distinction changes who you page, where you look, and which signals you correlate with.
What it is and why it matters
ceph_rgw_failed_req is a per-instance counter exposed by each RGW daemon, scraped from the admin socket and labeled with instance_id (alongside ceph_daemon, instance, job). Its sibling counter is ceph_rgw_req, the total request count. The operational signal is the ratio between the two: a sustained failed-request rate above 5% of total traffic for more than 5 minutes is the playbook’s TICKET condition.
RGW is stateless, horizontally scalable, and almost always behind a load balancer. A single gateway dying does not produce a rising failed ratio; the LB routes around it. A rising ratio across multiple instance_id labels means clients are giving up on requests in flight, which means the gateways are taking too long to respond. The most common reasons for that are load-balancer timeouts and RADOS slowness, not RGW itself.
The misread is treating ceph_rgw_failed_req as an HTTP error counter. A request that completes with a 403, 404, or 500 increments ceph_rgw_req and does not touch failed_req. For HTTP status breakdown you need the RGW access log, not this metric.
How it works
The counter lives in the rgw perf counter section and is described in the schema as “Aborted requests.” It increments once per request where the RGW detects that the client has closed the connection before the response was fully sent. The mechanism: the RGW frontend is writing the response back to the socket, the write fails because the peer has gone away, and the request is accounted as aborted.
Two things follow. First, the increment is connection-level, not status-level. There is no notion of “the request was going to be a 500” in this counter. Second, the counter increments even when the RGW is still working on the request. If RADOS takes 60 seconds to satisfy a GET and the client (or its load balancer) gives up at 30 seconds, the RGW eventually notices the dead socket and increments failed_req.
flowchart TD
A[Client opens HTTP connection] --> B[LB forwards to RGW]
B --> C[RGW processes request]
C --> D[RGW issues RADOS op]
D --> E{Client still connected
when response is ready?}
E -->|Yes| F[RGW sends response
2xx, 4xx, or 5xx]
F --> G["ceph_rgw_req++
failed_req NOT touched"]
E -->|No, client closed first| H[Socket write fails]
H --> I["ceph_rgw_req++
ceph_rgw_failed_req++"]failed_req is a “client gave up” signal, not a “RGW returned an error” signal. The cause can be anywhere on the path: the load balancer’s timeout, the network between LB and RGW, the RGW’s own processing, or the RADOS op sitting in an OSD queue for 40 seconds.
What does NOT increment the counter is just as important:
- HTTP 4xx and 5xx responses. A request that completes with a non-2xx status is a completed request. It increments
ceph_rgw_reqonly. A 404 for a missing key and a 500 from a handler bug both leavefailed_requntouched. - Rate-limited requests. When
rgw_max_concurrent_requestsis exceeded, RGW returns a 503 with an internalERR_RATE_LIMITED(-2218). These are completed responses with an error status, not aborted connections. - RADOS timeouts that hang the RGW. There is a reported failure mode where a
get_objoperation times out against RADOS (op status -110) and the RGW reportshttp_status=200while sending no body. The connection was not aborted by the client, so this does not show up infailed_requntil the client eventually gives up. If clients have very long or no timeouts, this class of bug can hide entirely from the ratio alert.
Where it shows up in production
The dominant cause of an elevated failed_req ratio is load-balancer timeouts. HAProxy, nginx, F5, or cloud LBs with timeout server or timeout client settings shorter than the worst-case RGW response time will abort connections whenever the backend is slow. The RGW log typically shows ERROR: STREAM_IO with err_no=-5 (EPIPE) in these cases. From the RGW’s perspective, this looks identical to a real client disconnect.
The second most common cause is genuine RADOS slowness pushing RGW response times past client or LB timeouts. The failed ratio rises on RGW, but the cause is a hot OSD, a BlueStore compaction stall, a nearfull cluster throttling backfill, or an OMAP storm on a bucket index. The RGW is the messenger.
The third pattern is RGW-local saturation. The SimpleThrottler has a reported hard ceiling on outstanding requests, and there have been reports of RGWs reaching that limit and failing to recover, causing all new connections to be aborted. This is rarer but worth checking when the failed ratio is concentrated on a single instance_id while sibling gateways are healthy.
A diagnostic trap: RGW does not always log the actual HTTP return code when a client disconnects mid-request. The access log may show a misleading status (a 403 or similar) instead of an explicit abort marker, which makes pure log analysis unreliable for explaining a failed_req spike. Trust the metric for the count; use the log for the request shape, not the outcome.
Tradeoffs and common misuses
Alerting on absolute rate instead of ratio. A flat threshold like rate(ceph_rgw_failed_req[5m]) > 10 fires differently on a 100 RPS site and a 10,000 RPS site. The ratio alert (> 0.05 sustained for 300 seconds) normalizes for traffic volume and is the form the playbook specifies. Reserve absolute-rate alerts for capacity-style ceilings you have tuned to your site.
Treating the ratio as an HTTP error rate. If you report “RGW error rate is 4%” based on failed_req / req, you are reporting the abort rate, not the error rate. For HTTP errors you need access-log-derived metrics, grouped by status code class.
Aggregating across instance_id blindly. Summing failed_req across all RGW instances is fine for a cluster-wide ticket, but it hides single-gateway failures. A misbehaving gateway behind a round-robin LB will show as a moderate cluster-wide ratio when it is really one bad instance. Always break the ratio down by instance_id during investigation.
Alerting without correlation. A failed_req ticket with no OSD, capacity, or OMAP context attached sends engineers to the RGW hosts first. Most of the time the RGW hosts are fine. Wire the alert to carry (or link to) the concurrent state of ceph_osd_commit_latency_ms, ceph_healthcheck_slow_ops, nearfull status, and LARGE_OMAP_OBJECTS.
Ignoring cold-start noise. A rolling RGW restart will briefly spike failed_req as in-flight requests lose their backend. The 300-second sustain window in the ticket condition is partly there to absorb this.
Signals to watch in production
| Signal | Why it matters | Warning sign |
|---|---|---|
rate(ceph_rgw_failed_req[5m]) / rate(ceph_rgw_req[5m]), per instance_id | The ticket ratio, normalized for traffic volume | Sustained above 0.05 for more than 300 seconds |
ceph_rgw_qlen, ceph_rgw_qactive | RGW queue depth and active request count | qlen climbing while req rate is flat means RGW is stuck waiting on RADOS |
ceph_rgw_get_initial_lat_sum / _count, ceph_rgw_put_initial_lat_sum / _count | RGW-side GET and PUT latency | Latency rising in lockstep with the failed_req ratio implicates the gateway’s downstream, not client behavior |
ceph_osd_commit_latency_ms, ceph_osd_apply_latency_ms, per OSD | Backend latency that RGW inherits | Outlier OSDs (5x cluster median for their device class) starve the PGs that RGW reads from |
ceph_healthcheck_slow_ops | Stuck I/O that will eventually push RGW past client timeouts | Any nonzero value sustained more than 120 seconds |
ceph_health_detail{name="OSD_NEARFULL"} or OSD_FULL | Capacity pressure that throttles recovery and starves client I/O | Active warnings, especially with backfill_toofull PGs present |
ceph_health_detail{name="LARGE_OMAP_OBJECTS"} | OMAP storm on a bucket index, stalling LIST and indexed operations | Active warning on an RGW index pool |
LB logs (HAProxy TERM status, nginx 499) | Client-side disconnects as seen from the load balancer | Spike in TERM/499 counts at the same moment the failed_req ratio rises confirms an LB-timeout cascade |
How Netdata helps
- Netdata scrapes
ceph_rgw_reqandceph_rgw_failed_reqper second and preserves theinstance_idlabel, so the ratio alert fires on the right gateway and the per-instance breakdown is one click away. - Per-second granularity catches the LB-timeout cascade pattern, where aborts cluster in tight bursts that minute-bucketed scrapers smear into noise.
- RGW metrics sit alongside OSD latency, slow-ops, capacity, and OMAP health signals in the same node view, so the natural first move on a
failed_reqticket is to check RADOS rather than SSH into the RGW host. - ML anomaly detection on the failed ratio and on RGW queue depth flags slow drift that precedes a hard ticket, typical of the capacity-and-RADOS-slowness class where the ratio creeps up over hours.
- Correlating RGW abort rate with LB-side connection metrics on the load-balancer hosts distinguishes “LB gave up first” from “RGW never responded” without requiring log scraping.
Related guides
- Ceph backfill_toofull: recovery blocked because target OSDs are full
- Ceph blocked ops: client I/O stuck behind a single slow OSD
- Ceph BlueStore RocksDB compaction stalls: periodic latency spikes
- Ceph BLUEFS_SPILLOVER: RocksDB metadata spilling onto the slow device
- Ceph BlueStore allocator fragmentation: rising latency at moderate fullness
- Ceph capacity death spiral: an OSD fails and recovery has nowhere to go
- Ceph client latency vs OSD latency: fast disks, slow clients
- Ceph deep scrub performance impact: I/O saturation that mimics an incident
- Ceph degraded objects: reduced redundancy and the race against a second failure
- Ceph FS_DEGRADED: standby MDS failed to take over a rank
- Ceph health detail: mapping ceph_health_detail checks to a cause
- Ceph HEALTH_ERR: reading the umbrella status and finding the real fault






