How to Fix Google's 429 'Unusual Traffic' Error in 2026
"Our systems have detected unusual traffic from your computer network." If you scrape Google, you have met this page, and it usually arrives with an HTTP 429 and a small wave of panic. The scraper that worked five minutes ago now returns nothing.
The good news: a 429 is a soft block, not a ban. Google is asking you to slow down, not locking you out forever. The bad news: most people respond by hammering retries or bolting on a proxy, both of which can make it worse.
This guide explains exactly what triggers the 429, how to read the signals Google gives you, and a resilient retry strategy — including how to tell whether your problem is the IP or your own request.
TL;DR: The 429 "unusual traffic" page is a soft block triggered by request rate, fingerprint, or IP reputation. Honor the Retry-After header if present; otherwise back off exponentially with jitter (1s, 2s, 4s, 8s + random). Always check the body, because Google sometimes serves the block page with a 200. Diagnose IP vs fingerprint by testing a clean IP — if the block follows you, rotating proxies won't help. Pace at human speed, fix your fingerprint, then add proxies for volume.
What triggers the 429
There is no public "X requests per hour" limit you can stay under, which is what makes the 429 feel random. In reality it fires on a blend of three signals, and each one is a different fix.
Request rate. Too many searches too fast from one source is the most common trigger. Fingerprint. A request that does not look like a real browser — a Python requests TLS handshake, a missing header, a headless tell — raises suspicion even at low volume. IP reputation. Datacenter ranges and abused proxy pools start with a strike against them before you send anything.
| Trigger | What sets it off | The fix |
|---|---|---|
| Request rate | Too many queries too fast from one source | Pace at human speed with randomized delays |
| Fingerprint | Non-browser TLS, missing headers, headless tells | Use a real browser; hide automation signals |
| IP reputation | Datacenter or abused proxy ranges | Rotate clean residential IPs |
Google weighs all three together, so two scrapers sending the same volume can get wildly different results based on fingerprint and IP. That is also why there is no single magic setting — you manage all three. The same triad drives DuckDuckGo's 202 Ratelimit; the status code differs, the causes do not.
Reading the block correctly
Before reacting, read what Google actually sent. Two headers and one body check tell you almost everything.
The status is usually 429 (too many requests) or sometimes 503. If a Retry-After header is present, it is a gift: it tells you exactly how long to wait, and honoring it precisely is the single most cooperative thing you can do. And critically, you must read the response body too — because Google sometimes serves the "unusual traffic" page or a CAPTCHA with a 200 OK, which fools any scraper that only checks the status code.
Fix one: pace like a human
The highest-impact change is also the simplest: slow down. A human searching does not fire ten queries in one second, and the community norm for sustainable Google scraping is on the order of several seconds between page requests — often quoted in the 7-to-17-second range — with randomization so the rhythm is not robotic.
import time, random
def polite_delay(low=7, high=17):
"""Wait a human-like, randomized interval between requests."""
time.sleep(random.uniform(low, high))
That randomized gap matters more than any header trick. Request rhythm — the regularity of your timing, not just its speed — is one of the strongest automation signals, so a fixed sleep(10) is more suspicious than a jittered one. Slower and irregular beats fast and metronomic every time.
Fix two: backoff with jitter
When you do get a 429, never retry immediately — an instant retry confirms you are a bot and extends the block. Back off exponentially, add jitter, and honor Retry-After when it is present:
import time, random, requests
def fetch_with_backoff(url, headers=None, max_tries=5):
for attempt in range(max_tries):
resp = requests.get(url, headers=headers, timeout=20)
if resp.status_code == 200 and not is_block_page(resp.text):
return resp
# Honor Retry-After if the server tells us how long to wait
retry_after = resp.headers.get("Retry-After")
if retry_after and retry_after.isdigit():
delay = int(retry_after)
else:
delay = (2 ** attempt) + random.uniform(0, 1.5) # 1s, 2s, 4s...
print(f"blocked (status {resp.status_code}), waiting {delay:.1f}s")
time.sleep(delay)
raise RuntimeError("still blocked after retries")
Exponential backoff gives the rate counter time to cool while jitter stops a fleet of workers from retrying in lockstep. For browser-based scraping the same logic lives in Node:
async function fetchWithRetry(url, opts = {}, tries = 5) {
for (let i = 0; i < tries; i++) {
const res = await fetch(url, opts);
if (res.status === 200) return res;
if (res.status === 429 || res.status === 503) {
const ra = res.headers.get('retry-after');
const wait = ra ? Number(ra) * 1000 : 2 ** i * 1000 + Math.random() * 1000;
await new Promise((r) => setTimeout(r, wait));
continue;
}
return res; // some other status — let the caller decide
}
throw new Error('still blocked after retries');
}
Detecting a block hidden in a 200
This is the bug that makes scrapers lie to you. Google can return its block or CAPTCHA page with a 200, so status-code-only logic records "success" while your dataset fills with empty rows. Detect it by scanning the body for known markers:
BLOCK_MARKERS = (
"unusual traffic",
"/sorry/",
"detected unusual traffic",
"not a robot",
"our systems have detected",
)
def is_block_page(html):
low = html.lower()
if any(marker in low for marker in BLOCK_MARKERS):
return True
# A popular query returning zero results is almost always a block
if "did not match any documents" in low:
return True
return False
Wire is_block_page into every fetch, as the backoff function above does. Treating a marker-laden 200 as a failure is what keeps a silent block from poisoning your data — the same class of silent failure covered in why your SERP scraper breaks at 3 a.m.
Is it the IP or the fingerprint?
This is the question that decides whether spending money on proxies will help or just waste it. The diagnosis is a simple experiment.
Take a query that is currently getting blocked and run it once from a fresh, clean IP — a different network, a mobile hotspot, a new residential proxy. If it succeeds, your problem is IP reputation, and rotation or better proxies will help. If the same request gets blocked on the new IP too, the trigger is the request itself — your rate, headers, or browser fingerprint — and adding proxies will only burn fresh IPs at the same rate.
Most stubborn 429s that survive a proxy swap are fingerprint problems. The fix there is making your browser look genuinely human — the detailed work of hiding navigator.webdriver, matching headers, and the rest, covered in beating headless Chrome detection. And even with a clean fingerprint, a clean IP does not save a robotic rhythm, which is the lesson of why proxies get banned.
A resilient retry wrapper
Put the pieces together and you get a small, reusable layer that paces, backs off, honors Retry-After, and refuses to be fooled by a 200 block page. That wrapper is the difference between a scraper that dies on the first 429 and one that rides through soft blocks gracefully.
The honest ceiling: no wrapper makes a single IP infinite. Backoff buys resilience, not unlimited volume. Past a few hundred queries a day you are managing a rotating proxy pool and a fingerprint strategy as much as a scraper — which is the point where many teams decide the 429 is somebody else's problem to solve. That is the build-vs-buy line we draw in scraping Google for free.
The zero-429 alternative
If "unusual traffic" pages are eating your week, a SERP API removes them from your side of the wire entirely. The access — rate management, IP rotation, fingerprinting, CAPTCHA handling — is handled, and you get clean JSON:
import requests
resp = requests.get(
"https://api.apiserpent.com/api/search",
headers={"X-API-Key": "sk_live_your_key"},
params={"q": "best running shoes 2026", "engine": "google", "country": "us"},
)
data = resp.json()
for r in data["results"]["organic"]:
print(r["position"], r["title"], r["url"])
No backoff loops, no block-page detection, no IP-vs-fingerprint debugging — just results, across Google, Bing, Yahoo, and DuckDuckGo. Try a query in the playground to see the response shape.
Never see "unusual traffic" again.
Serpent handles rate-limits, IP rotation, fingerprints, and CAPTCHAs for you and returns clean JSON for Google, Bing, Yahoo, and DuckDuckGo. Get 10 free Google searches on signup, then pay-as-you-go from $0.03 per 10,000 searches at scale, with no subscription.
Get Your Free API KeyExplore: Google SERP API · All SERP APIs · Pricing
FAQ
How long should I wait after a 429 from Google?
If the response carries a Retry-After header, honor it exactly. If not, back off exponentially with jitter — wait roughly 1, 2, 4, 8 seconds plus a random fraction across retries — rather than retrying instantly. There is no published fixed limit; the block clears on its own, and immediate retries just confirm you are automated and prolong it.
Is the 429 my IP or my code?
Test it. If a request from a fresh, clean IP succeeds where your usual one fails, it is an IP-reputation problem and you need rotation or a better proxy. If the block follows you to a new IP, the trigger is your request itself — rate, headers, or browser fingerprint — and rotating IPs only burns them faster. Diagnose before you throw proxies at it.
Does adding a proxy fix a 429 instantly?
Only if the cause was IP reputation. A clean residential IP resets the rate counter, but if your headers, timing, or fingerprint look robotic, the new IP gets flagged just as fast — you have simply moved the problem and started spending money on burned IPs. Fix pacing and fingerprint first, then add proxies for volume.
Why do I get a 200 OK that is actually a block page?
Google sometimes serves its "unusual traffic" or CAPTCHA page with a 200 status code, so a scraper that only checks the status thinks it succeeded while collecting zero results. Always inspect the body for block markers like "unusual traffic" or a /sorry/ redirect, and treat an empty result set on a popular query as a block.



