amir20/dozzle · warning

cloud rate limited, backing off for

Error message

cloud rate limited, backing off for %s

What it means

Dozzle Cloud responded with HTTP 429 Too Many Requests. The dispatcher stores a blockedUntil timestamp (Retry-After header if present, otherwise 60s), logs a warning that the circuit breaker tripped, and returns this error for the current notification, which is dropped.

Solutions

  1. Respect the backoff: don't resend until the breaker window (Retry-After or default 60s) passes
  2. Throttle or debounce notification rules so volume stays under the cloud quota
  3. Batch related notifications instead of sending one per event
  4. Check your Dozzle Cloud plan limits if 429s recur regularly
Defensive patterns

Strategy: retry

Try / catch

if err := d.Send(ctx, n); err != nil {
    if strings.Contains(err.Error(), "backing off") {
        // respect the 60s+ backoff; queue and resend later
    }
}

Prevention

When it happens

Trigger: CloudDispatcher.Send received resp.StatusCode == 429 from the cloud /api/events endpoint, i.e. the account exceeded the cloud-side notification rate limit.

Common situations: Notification rules that fire on every log line; many containers emitting alerts simultaneously; retry loops without backoff hammering the endpoint; shared/demo API keys near quota.

Related errors


AI-assisted analysis of amir20/dozzle@d9463cbe21 (2026-09-07). Data as JSON: /api/errors/b669a076325263be. Report an issue: GitHub.

Appendix: source

Thrown at internal/notification/dispatcher/cloud.go:110

	resp, err := c.client.Do(req)
	if err != nil {
		return fmt.Errorf("failed to send to cloud: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode == http.StatusTooManyRequests {
		retryAfter := defaultRetryAfter
		if ra := resp.Header.Get("Retry-After"); ra != "" {
			if seconds, err := strconv.Atoi(ra); err == nil {
				retryAfter = time.Duration(seconds) * time.Second
			}
		}
		c.blockedUntil.Store(time.Now().Add(retryAfter).UnixNano())
		log.Warn().
			Str("cloud", c.Name).
			Dur("retry_after", retryAfter).
			Msg("rate limited by cloud, circuit breaker tripped")
		return fmt.Errorf("cloud rate limited, backing off for %s", retryAfter)
	}

	if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
		limitedReader := io.LimitReader(resp.Body, 1024*1024)
		responseBody, _ := io.ReadAll(limitedReader)
		c.blockedUntil.Store(time.Now().Add(unauthorizedRetryAfter).UnixNano())
		log.Warn().
			Str("cloud", c.Name).
			Int("status_code", resp.StatusCode).
			Dur("retry_after", unauthorizedRetryAfter).
			Msg("cloud rejected API key, circuit breaker tripped")
		return fmt.Errorf("cloud returned status code %d: %s", resp.StatusCode, string(responseBody))
	}

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		limitedReader := io.LimitReader(resp.Body, 1024*1024)
		responseBody, _ := io.ReadAll(limitedReader)
		log.Debug().

View on GitHub (pinned to d9463cbe21)