← Recipes

阅读中文版本

Debug HTTP 503 with Retry-After Backoff

Reproduce a 503 response, read the Retry-After header, and prove a client backoff schedule respects server guidance under load.

~30 minhttpapidebuggingresilience

Task

Capture a 503 response together with its Retry-After header, then script a retry schedule that honours the server-provided delay.

When to use this

A partner API returns 503 during peak hours but the client retries immediately, amplifying the outage instead of easing it.

Prerequisites

  • curl 8.x and jq installed
  • A staging endpoint that can be forced to return 503
  • A client retry loop you can modify

Steps

1. Reproduce the failure and record the status line plus response headers.

curl -sS -D - -o /dev/null -w 'status=%{http_code}\n' \
  -H 'Accept: application/json' \
  https://api.staging.example.com/v1/reports

Expected output: The output shows status=503 and a Retry-After header expressed in seconds or as an HTTP date.

2. Read the Retry-After value and sleep exactly that long before the next attempt.

retry_after=$(curl -sSI https://api.staging.example.com/v1/reports | awk -F': ' 'tolower($1)=="retry-after"{print $2}' | tr -d '\r')
sleep "${retry_after:-5}"
echo "retrying after ${retry_after:-5}s"

Expected output: The client waits the server-provided delay instead of a fixed short interval.

3. Add jittered exponential backoff that never exceeds the header value.

def backoff(attempt, retry_after):
    base = min(2 ** attempt, retry_after)
    return base + random.uniform(0, base * 0.2)

Expected output: Attempts spread out so recovering services are not hit by a synchronized retry storm.

Troubleshooting

Problem

Retry-After is an HTTP date, not seconds

Solution

Parse it with a date library and subtract the current time instead of assuming an integer.

Problem

The client retries during declared maintenance

Solution

Treat 503 with Retry-After as authoritative and gate the loop on the header.

Problem

Retries succeed but create duplicate records

Solution

Send an idempotency key so a retried request cannot double-apply.

Limitations

  • Covers HTTP-level retry signals only; application error codes need their own policy.
  • Assumes the server emits Retry-After; otherwise fall back to exponential backoff.
  • Does not model server queue depth, so aggressive retries can still overload a struggling service.

What this recipe does

Capture a 503 response together with its Retry-After header, then script a retry schedule that honours the server-provided delay.

A partner API returns 503 during peak hours but the client retries immediately, amplifying the outage instead of easing it.

Prerequisites

  • curl 8.x and jq installed
  • A staging endpoint that can be forced to return 503
  • A client retry loop you can modify

Procedure

  1. Reproduce the failure and record the status line plus response headers.
curl -sS -D - -o /dev/null -w 'status=%{http_code}\n' \
  -H 'Accept: application/json' \
  https://api.staging.example.com/v1/reports

Expected result: The output shows status=503 and a Retry-After header expressed in seconds or as an HTTP date.

  1. Read the Retry-After value and sleep exactly that long before the next attempt.
retry_after=$(curl -sSI https://api.staging.example.com/v1/reports | awk -F': ' 'tolower($1)=="retry-after"{print $2}' | tr -d '\r')
sleep "${retry_after:-5}"
echo "retrying after ${retry_after:-5}s"

Expected result: The client waits the server-provided delay instead of a fixed short interval.

  1. Add jittered exponential backoff that never exceeds the header value.
def backoff(attempt, retry_after):
    base = min(2 ** attempt, retry_after)
    return base + random.uniform(0, base * 0.2)

Expected result: Attempts spread out so recovering services are not hit by a synchronized retry storm.

How to confirm it worked

Run the commands above and confirm every expected result in order. If a step does not produce the stated output, use the troubleshooting notes before continuing. A passing run shows the retry schedule matches the Retry-After value and the number of requests during a simulated outage drops.

Troubleshooting

Retry-After is an HTTP date, not seconds — Parse it with a date library and subtract the current time instead of assuming an integer.

The client retries during declared maintenance — Treat 503 with Retry-After as authoritative and gate the loop on the header.

Retries succeed but create duplicate records — Send an idempotency key so a retried request cannot double-apply.

Limitations

  • Covers HTTP-level retry signals only; application error codes need their own policy.
  • Assumes the server emits Retry-After; otherwise fall back to exponential backoff.
  • Does not model server queue depth, so aggressive retries can still overload a struggling service.