Cloudflare API Timeout: Fix 524, 522, and Gateway Timeout Errors
Fix Cloudflare API timeout errors (524, 522, 504) with step-by-step diagnosis, origin tuning, Worker limits, and retry logic. Resolve in under 30 minutes.
- Error 524 (A Timeout Occurred) means Cloudflare established a TCP connection to your origin but the origin did not respond within Cloudflare's 100-second proxy read timeout — the origin is alive but too slow.
- Error 522 (Connection Timed Out) means Cloudflare could not complete a TCP handshake with your origin within 15 seconds — the origin is unreachable or a firewall is dropping packets.
- Client-side timeouts against api.cloudflare.com itself are usually caused by large paginated responses, rate-limit back-off misconfigurations, or network path issues between your client and Cloudflare's API edge.
- Quick fix for 524: increase origin processing time by optimizing slow database queries or long-running jobs, or offload work to a background queue and return an immediate 202 Accepted.
- Quick fix for 522: verify origin IP is reachable from Cloudflare's IP ranges, check security-group and firewall rules, and confirm your DNS A/AAAA record points to the correct origin IP.
| Method | When to Use | Time to Implement | Risk |
|---|---|---|---|
| Optimize slow origin query/endpoint | 524 caused by a slow DB query or synchronous job | 30 min – 2 hrs | Low — no infrastructure change |
| Return 202 + async background job | Long-running work that cannot be sped up | 1–4 hrs | Medium — requires job queue and polling endpoint |
| Increase Cloudflare proxy timeout via Enterprise support or Cloudflare Timeout setting | Legitimate long-poll or large file transfer endpoints | 15 min (config change) | Low — only affects that zone/route |
| Fix origin firewall / security-group to allow Cloudflare IPs | 522 Connection Timed Out — TCP handshake failing | 15 min | Low — whitelist-only change |
| Add retry logic with exponential back-off on client | Intermittent timeouts from api.cloudflare.com or transient origin issues | 1–2 hrs | Low — client-side only |
| Move heavy logic into Cloudflare Workers with Durable Objects or queues | Worker CPU limit (50 ms) or subrequest timeout violations | 4–8 hrs | Medium — architectural change |
| Enable Argo Smart Routing | Timeout caused by a poor network path between Cloudflare and origin | 5 min (toggle) | Low — paid add-on |
Understanding Cloudflare API Timeout Errors
Cloudflare sits between your clients and your origin server as a reverse proxy. When a timeout occurs, the error can originate from three distinct layers: (1) the network path between the client and Cloudflare's edge, (2) the proxy connection between Cloudflare and your origin, or (3) the Cloudflare API itself (api.cloudflare.com) when you are programmatically managing your zone. Each layer produces a different HTTP status code and requires a different fix.
The Four Timeout Error Codes You Will Encounter
524 — A Timeout Occurred
Error 524
A timeout occurred
cloudflare-nginx
Cloudflare successfully opened a TCP connection to your origin but did not receive a complete HTTP response within the proxy read timeout (100 seconds on Free/Pro/Business; configurable on Enterprise). The origin is alive and reachable but is processing too slowly.
522 — Connection Timed Out
Error 522
Connection timed out
cloudflare-nginx
Cloudflare could not complete the TCP three-way handshake with your origin within 15 seconds. Common causes: the origin IP has changed, a firewall or security group is blocking Cloudflare's IP ranges, or the origin process is down.
504 — Gateway Timeout This is the generic HTTP/1.1 version you may see from upstream proxies or load balancers sitting in front of your application. Diagnosis steps are the same as for 524.
Client Timeout Hitting api.cloudflare.com
When you are calling https://api.cloudflare.com/client/v4/... from automation and your HTTP client times out, the causes differ: extremely large paginated responses, network path congestion, or incorrectly configured client-side timeout values.
Step 1: Identify Which Layer Is Timing Out
Open your browser developer tools or run curl -v and capture the full response headers. The cf-ray header confirms Cloudflare processed the request. The HTTP status code tells you which layer failed.
curl -v -o /dev/null -s -w "%{http_code} %{time_total}s\n" https://your-domain.com/slow-endpoint
If you see 524 in the response body and a cf-ray header, Cloudflare reached your origin but it timed out — jump to Step 3.
If you see 522, your origin was not reachable at all — jump to Step 4.
If curl itself hangs and you are calling api.cloudflare.com, jump to Step 5.
Step 2: Check the Cloudflare Error Log and Origin Access Logs
In the Cloudflare dashboard, navigate to Analytics → Traffic and filter by the time window. Look for a spike in 5xx errors. Simultaneously check your origin's access log:
# Nginx
tail -n 200 /var/log/nginx/access.log | grep ' 52[24] '
# Apache
tail -n 200 /var/log/apache2/access.log | awk '$9 >= 524'
# Node.js / PM2
pm2 logs --lines 100 | grep -i timeout
If the request never appears in your origin log, the connection is failing before it reaches your application (522 scenario). If it appears and shows a long response time, the origin is the bottleneck (524 scenario).
Step 3: Fix 524 — Origin Is Too Slow
3a. Profile the slow endpoint
Use curl with time breakdown flags to isolate where time is spent:
curl -w "\nDNS: %{time_namelookup}s\nConnect: %{time_connect}s\nTLS: %{time_appconnect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" \
-o /dev/null -s https://your-origin-ip/slow-endpoint
If time_starttransfer is > 5 seconds, the bottleneck is server-side processing.
3b. Add a database slow-query log
-- PostgreSQL: enable slow query logging
ALTER SYSTEM SET log_min_duration_statement = '1000'; -- log queries > 1s
SELECT pg_reload_conf();
-- MySQL / MariaDB
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
3c. Async pattern — return 202 immediately
If the operation genuinely takes more than 30 seconds (report generation, bulk export, ML inference), refactor the endpoint to enqueue work and return a job ID:
# Flask example
@app.route('/api/v1/export', methods=['POST'])
def start_export():
job_id = queue.enqueue(run_export, request.json)
return jsonify({'job_id': job_id, 'status': 'queued'}), 202
@app.route('/api/v1/export/<job_id>', methods=['GET'])
def poll_export(job_id):
job = queue.fetch_job(job_id)
return jsonify({'status': job.get_status(), 'result': job.result})
3d. Enterprise — increase proxy timeout
If you are on Enterprise, open a support ticket or use the Cloudflare API to set a custom read timeout on a specific route via a Ruleset rule. Free/Pro/Business zones are fixed at 100 seconds.
Step 4: Fix 522 — Origin Unreachable
4a. Verify Cloudflare can reach your origin IP
Cloudflare publishes its egress IP ranges. Your origin firewall must allow inbound traffic from these ranges on port 80 and 443.
# Download Cloudflare's current IP ranges
curl -s https://www.cloudflare.com/ips-v4 > cf_ipv4.txt
curl -s https://www.cloudflare.com/ips-v6 > cf_ipv6.txt
# Check if your origin is currently blocking them (run from a Cloudflare IP or use a VPN)
nc -zv your-origin-ip 443
4b. Update your AWS security group (example)
# Add all Cloudflare IPv4 ranges to your security group
while IFS= read -r cidr; do
aws ec2 authorize-security-group-ingress \
--group-id sg-0123456789abcdef0 \
--protocol tcp --port 443 \
--cidr "$cidr"
done < cf_ipv4.txt
4c. Confirm DNS A record matches your real origin IP
# Check what IP Cloudflare's DNS is returning (orange-cloud)
dig +short your-domain.com
# Check what IP is stored in Cloudflare DNS records via API
curl -s -X GET "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records?type=A&name=your-domain.com" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" | jq '.result[].content'
If the IP returned does not match your origin, update the A record.
Step 5: Fix Client Timeouts Against api.cloudflare.com
If your automation or CI pipeline is timing out while calling the Cloudflare API directly:
5a. Check rate limit headers
Cloudflare API enforces rate limits (1200 requests per 5 minutes per user globally). Exceeding this returns 429 Too Many Requests — some HTTP clients interpret a stalled 429 response as a timeout.
curl -s -I -X GET "https://api.cloudflare.com/client/v4/zones" \
-H "Authorization: Bearer YOUR_API_TOKEN" | grep -i 'x-ratelimit\|retry-after'
5b. Implement exponential back-off
import time, requests
def cf_api_get(url, token, max_retries=5):
for attempt in range(max_retries):
try:
resp = requests.get(
url,
headers={'Authorization': f'Bearer {token}'},
timeout=30 # always set an explicit client timeout
)
if resp.status_code == 429:
wait = int(resp.headers.get('Retry-After', 2 ** attempt))
time.sleep(wait)
continue
resp.raise_for_status()
return resp.json()
except requests.exceptions.Timeout:
if attempt == max_retries - 1:
raise
time.sleep(2 ** attempt)
5c. Paginate large list responses
Large zones with thousands of DNS records, firewall rules, or cache rules can produce slow list-API responses. Always paginate:
# Paginate DNS records — page through 100 at a time
PAGE=1
while true; do
RESULT=$(curl -s "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records?per_page=100&page=$PAGE" \
-H "Authorization: Bearer $CF_TOKEN")
TOTAL_PAGES=$(echo "$RESULT" | jq '.result_info.total_pages')
echo "$RESULT" | jq '.result[]'
[ "$PAGE" -ge "$TOTAL_PAGES" ] && break
PAGE=$((PAGE + 1))
done
Step 6: Cloudflare Workers-Specific Timeouts
If you are running logic inside a Cloudflare Worker, you face two hard limits:
- CPU time: 10 ms (Free) / 50 ms (Paid) per request
- Wall-clock time for
fetch()subrequests: 30 seconds
If your Worker times out, you will see:
Error 1101: Worker threw an exception
or a 522/524 when the Worker's subrequest to your origin times out.
Move expensive logic to Durable Objects or use Cloudflare Queues to fan out work asynchronously. Always set an AbortController signal on subrequests:
export default {
async fetch(request, env) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 25000); // 25s safety margin
try {
const resp = await fetch('https://your-origin.com/api/data', {
signal: controller.signal
});
return resp;
} catch (e) {
if (e.name === 'AbortError') {
return new Response('Origin timeout', { status: 504 });
}
throw e;
} finally {
clearTimeout(timeoutId);
}
}
};
Frequently Asked Questions
#!/usr/bin/env bash
# cloudflare-timeout-diagnosis.sh
# Run this script from a server that has network access to your origin.
# Set CF_TOKEN, ZONE_ID, and ORIGIN_IP before running.
CF_TOKEN="${CF_TOKEN:?Set CF_TOKEN}"
ZONE_ID="${ZONE_ID:?Set ZONE_ID}"
ORIGIN_IP="${ORIGIN_IP:?Set ORIGIN_IP}"
DOMAIN="${DOMAIN:-example.com}"
echo "=== 1. TIMING BREAKDOWN ==="
curl -w "DNS: %{time_namelookup}s\nTCP Connect: %{time_connect}s\nTLS Handshake: %{time_appconnect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\nHTTP Status: %{http_code}\n" \
-o /dev/null -s "https://${DOMAIN}/"
echo ""
echo "=== 2. DIRECT ORIGIN TIMING (bypass Cloudflare) ==="
curl -w "TCP Connect: %{time_connect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" \
-o /dev/null -s -H "Host: ${DOMAIN}" "https://${ORIGIN_IP}/" \
--resolve "${DOMAIN}:443:${ORIGIN_IP}" --insecure
echo ""
echo "=== 3. TCP REACHABILITY ON PORT 443 ==="
nc -zv -w 5 "${ORIGIN_IP}" 443 && echo "PORT OPEN" || echo "PORT CLOSED/FILTERED"
echo ""
echo "=== 4. VERIFY CLOUDFLARE IP RANGES ARE WHITELISTED ==="
CF_IPS=$(curl -s https://www.cloudflare.com/ips-v4)
echo "Cloudflare IPv4 ranges:"
echo "${CF_IPS}"
echo "Verify these are allowed inbound on port 443 in your firewall/security-group."
echo ""
echo "=== 5. CHECK DNS A RECORD VIA CLOUDFLARE API ==="
curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records?type=A&name=${DOMAIN}" \
-H "Authorization: Bearer ${CF_TOKEN}" \
-H "Content-Type: application/json" | jq '.result[] | {name, content, proxied}'
echo ""
echo "=== 6. CHECK RECENT 5XX ANALYTICS ==="
START=$(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-1H +%Y-%m-%dT%H:%M:%SZ)
END=$(date -u +%Y-%m-%dT%H:%M:%SZ)
curl -s -X GET \
"https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/analytics/dashboard?since=${START}&until=${END}&continuous=true" \
-H "Authorization: Bearer ${CF_TOKEN}" \
-H "Content-Type: application/json" | \
jq '.result.totals | {requests_522: .requests.status["522"], requests_524: .requests.status["524"], requests_504: .requests.status["504"]}'
echo ""
echo "=== 7. API RATE LIMIT STATUS ==="
curl -s -I -X GET "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}" \
-H "Authorization: Bearer ${CF_TOKEN}" | \
grep -i 'x-ratelimit-limit\|x-ratelimit-remaining\|x-ratelimit-reset\|cf-ray'
echo ""
echo "=== DIAGNOSIS COMPLETE ==="
echo "If Step 1 TTFB > 100s => 524 — optimize origin (Step 3 in the guide)"
echo "If Step 2 TCP Connect fails => 522 — fix firewall (Step 4 in the guide)"
echo "If Step 3 PORT CLOSED => 522 — firewall blocking Cloudflare IPs"
echo "If Step 5 IP is wrong => update A record via dashboard or API"Error Medic Editorial
Error Medic Editorial is a team of senior DevOps engineers, SREs, and cloud architects with collective experience spanning AWS, GCP, Azure, and Cloudflare infrastructure at Fortune 500 scale. We write from production war rooms, not from documentation alone — every troubleshooting guide is validated against real incident post-mortems.
Sources
- https://developers.cloudflare.com/support/troubleshooting/cloudflare-errors/troubleshooting-cloudflare-5xx-errors/
- https://developers.cloudflare.com/api/
- https://developers.cloudflare.com/workers/platform/limits/
- https://community.cloudflare.com/t/error-524-a-timeout-occurred/1455
- https://stackoverflow.com/questions/49841944/cloudflare-524-timeout-how-to-increase-timeout-on-free-plan