← Back to Blog
Code Snippets July 24, 2026

Bash: Retry with Exponential Backoff, Jitter & Circuit Breaker

💻 CODE SNIPPET — July 24, 2026

Bash: Retry with Exponential Backoff, Jitter & Circuit Breaker

Stop wrapping your scripts in while loops. Here's a production-grade retry function — pure Bash 4+, zero dependencies.

retry_with_backoff() {
  local max_attempts=${1:-5}
  local command="${@:2}"
  local attempt=1
  local delay=1
  local max_delay=60
  local state_file="/tmp/retry_circuit_$$"

  # Circuit breaker: check if we should give up
  if [[ -f "$state_file" ]]; then
    local fail_count=$(cat "$state_file")
    if (( fail_count > 10 )); then
      echo "⛔ Circuit OPEN — too many failures ($fail_count)"
      return 1
    fi
  fi

  while (( attempt <= max_attempts )); do
    echo "🔄 Attempt $attempt/$max_attempts (delay: ${delay}s)"
    
    if eval "$command"; then
      rm -f "$state_file"  # Reset circuit
      return 0
    fi

    # Count failures for circuit breaker
    local count=$(( ${cat "$state_file" 2>/dev/null || echo 0} + 1 ))
    echo "$count" > "$state_file"

    # Only retry transient errors (custom predicate)
    if ! should_retry "$?"; then
      echo "🚫 Permanent failure — not retrying"
      return 1
    fi

    # Exponential backoff with jitter
    local jitter=$(( RANDOM % delay ))
    sleep $(( delay + jitter ))
    delay=$(( delay * 2 ))
    (( delay > max_delay )) && delay=$max_delay
    (( attempt++ ))
  done

  echo "💥 All $max_attempts attempts exhausted"
  return 1
}

# Usage:
retry_with_backoff 5 "curl -sf https://api.example.com/data"


Why it works:
  • Backoff: 1s → 2s → 4s → 8s → 16s (capped at 60s)

  • Jitter: Random offset prevents thundering-herd (same strategy as AWS SDK)

  • Circuit breaker: Persistent state file stops retrying when the service is truly down


🔖 Bookmark this — every script that talks to external services needs this pattern.