Cloudflare Error 1020 Access Denied: Complete Fix Guide
If you’ve spent any time scraping the web, you’ve almost certainly encountered the dreaded “Error 1020 – Access Denied” page from Cloudflare. This error means Cloudflare’s firewall rules have identified your request as suspicious and blocked it outright.
Unlike Cloudflare’s JavaScript challenges (which give you a chance to prove you’re human), Error 1020 is a hard block. Your request is rejected with no opportunity to solve a challenge. This guide explains exactly why it happens and provides eight proven methods to fix it.
What Causes Cloudflare Error 1020?
Error 1020 is triggered by Cloudflare’s Web Application Firewall (WAF) rules. The site owner has configured specific rules that your request violates. Common triggers include:
1. Blocked IP Addresses or Ranges
The site owner has explicitly blocked your IP address or the entire IP range (common with datacenter IPs). Cloudflare’s firewall allows blocking by:
- Individual IPs
- CIDR ranges (e.g., entire /16 blocks from AWS or Google Cloud)
- ASN (Autonomous System Number) — blocking all IPs from a specific provider
- Country codes
2. Missing or Suspicious Headers
Firewall rules can inspect HTTP headers. Requests missing standard browser headers or containing automation-related headers get blocked:
# Headers that trigger 1020
User-Agent: python-requests/2.28.0
User-Agent: Go-http-client/1.1
User-Agent: (empty)3. Blocked HTTP Methods
Some sites block non-standard HTTP methods. If you’re sending OPTIONS, PUT, or DELETE requests to endpoints that expect only GET or POST, the WAF may trigger 1020.
4. URL Pattern Matching
Firewall rules can match URL patterns. Accessing paths like /api/, /admin/, or /wp-json/ with non-whitelisted IPs often triggers 1020.
5. Rate-Based Rules
Cloudflare’s rate limiting can escalate to a 1020 block if you exceed defined thresholds. Unlike Error 1015 (which is specifically rate-limit), 1020 can be the result of a custom rule that monitors request rates.
6. Bot Score Threshold
Cloudflare assigns each request a “bot score” from 1-99. Site owners can configure WAF rules to block requests below a certain score. Automated tools typically receive very low scores.
Diagnosing the Exact Cause
Before jumping to solutions, identify which firewall rule is blocking you. The error page sometimes includes a Ray ID — a unique identifier for the blocked request.
import requests
try:
response = requests.get("https://target-site.com")
print(f"Status: {response.status_code}")
print(f"CF-Ray: {response.headers.get('cf-ray', 'N/A')}")
except Exception as e:
print(f"Error: {e}")Check the response body for clues:
if response.status_code == 403:
if "error code: 1020" in response.text:
print("Cloudflare WAF rule triggered")
# Extract Ray ID for debugging
import re
ray_match = re.search(r'Ray ID: ([a-f0-9]+)', response.text)
if ray_match:
print(f"Ray ID: {ray_match.group(1)}")Fix 1: Switch to Residential Proxies
The most common cause of Error 1020 is using datacenter IPs. Switching to residential proxies immediately resolves this in most cases.
import requests
# Datacenter proxy (likely to trigger 1020)
dc_proxy = "http://user:pass@dc-proxy.provider.com:8080"
# Residential proxy (much less likely to trigger 1020)
res_proxy = "http://user:pass@res-proxy.provider.com:7777"
proxies = {
"http": res_proxy,
"https": res_proxy
}
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br"
}
response = requests.get(
"https://target-site.com",
headers=headers,
proxies=proxies
)
print(response.status_code)For recommendations on residential proxy providers, see our proxy provider reviews.
Fix 2: Rotate IPs with Every Request
If your current IP is already blocked, rotating to a fresh IP can bypass the block.
import requests
from itertools import cycle
proxy_list = [
"http://user:pass@gate.provider.com:7777",
"http://user:pass@gate.provider.com:7778",
"http://user:pass@gate.provider.com:7779",
]
proxy_pool = cycle(proxy_list)
def fetch_with_rotation(url, max_retries=5):
for attempt in range(max_retries):
proxy = next(proxy_pool)
proxies = {"http": proxy, "https": proxy}
try:
response = requests.get(url, proxies=proxies, headers=headers, timeout=15)
if response.status_code == 403 and "1020" in response.text:
print(f"1020 on attempt {attempt + 1}, rotating IP...")
continue
return response
except requests.exceptions.RequestException:
continue
return NoneRead our detailed guide on IP rotation strategies for advanced rotation patterns.
Fix 3: Fix Your Request Headers
Incomplete or incorrect headers are a frequent cause. Here’s a comprehensive header set that mimics a real Chrome browser:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br, zstd",
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1",
"Sec-Ch-Ua": '"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
"Sec-Ch-Ua-Mobile": "?0",
"Sec-Ch-Ua-Platform": '"Windows"',
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"Cache-Control": "max-age=0"
}Header Order Matters
Some WAF rules check header order. Real browsers send headers in a specific order. Python’s requests library uses alphabetical ordering by default, which can be suspicious. Use OrderedDict or libraries that preserve insertion order.
from collections import OrderedDict
import requests
session = requests.Session()
session.headers = OrderedDict([
("Host", "target-site.com"),
("Connection", "keep-alive"),
("Cache-Control", "max-age=0"),
("Upgrade-Insecure-Requests", "1"),
("User-Agent", "Mozilla/5.0 ..."),
("Accept", "text/html,..."),
("Sec-Fetch-Site", "none"),
("Sec-Fetch-Mode", "navigate"),
("Sec-Fetch-User", "?1"),
("Sec-Fetch-Dest", "document"),
("Accept-Encoding", "gzip, deflate, br"),
("Accept-Language", "en-US,en;q=0.9"),
])Fix 4: Use cURL-Impersonate for TLS Fingerprint Matching
Cloudflare’s WAF can detect mismatches between your claimed User-Agent and your actual TLS fingerprint. The curl_cffi library solves this.
from curl_cffi import requests as curl_requests
response = curl_requests.get(
"https://target-site.com",
impersonate="chrome",
proxies={"https": "http://user:pass@proxy:port"}
)
if response.status_code == 200:
print("Success! TLS fingerprint matched.")
else:
print(f"Status: {response.status_code}")For more on TLS fingerprinting, read our TLS/JA3 fingerprinting guide.
Fix 5: Browser Automation
When the WAF rule requires a real browser environment, use automation tools:
import undetected_chromedriver as uc
options = uc.ChromeOptions()
options.add_argument("--no-sandbox")
driver = uc.Chrome(options=options)
driver.get("https://target-site.com")
# Check if we bypassed the 1020
if "Access Denied" not in driver.page_source:
print("Successfully bypassed 1020!")
# Extract cookies for use with requests library
cookies = {c['name']: c['value'] for c in driver.get_cookies()}
print(f"Cookies: {cookies}")
driver.quit()See our Undetected ChromeDriver tutorial for complete setup instructions.
Fix 6: Change Geographic Location
Some WAF rules block entire countries. If you’re scraping from a blocked region, switch to a proxy in an allowed country.
# Use country-specific proxy targeting
proxies = {
"http": "http://user:pass@gate.provider.com:7777-country-us",
"https": "http://user:pass@gate.provider.com:7777-country-us"
}
# Try different countries if US is blocked
countries = ["us", "gb", "de", "ca", "au"]
for country in countries:
proxy = f"http://user:pass@gate.provider.com:7777-country-{country}"
try:
response = requests.get(
"https://target-site.com",
proxies={"http": proxy, "https": proxy},
headers=headers,
timeout=15
)
if response.status_code != 403:
print(f"Success with country: {country}")
break
except:
continueExplore our geo-specific proxy guides for country-targeted scraping.
Fix 7: Use the Site’s API Instead
Many websites that heavily protect their frontend have less restrictive APIs. Check for:
- REST API endpoints (look in Network tab of DevTools)
- GraphQL endpoints
- Mobile app APIs (often less protected)
# Instead of scraping the HTML page
# response = requests.get("https://site.com/products") # Triggers 1020
# Use the API endpoint
api_response = requests.get(
"https://api.site.com/v1/products",
headers={
"Authorization": "Bearer <token>",
"Accept": "application/json",
"User-Agent": "Mozilla/5.0 ..."
},
proxies=proxies
)
data = api_response.json()Fix 8: Session-Based Approach
Cloudflare WAF rules often allow requests that come from established sessions. First visit a non-protected page to establish cookies, then navigate to the protected one.
session = requests.Session()
session.headers.update(headers)
session.proxies.update(proxies)
# Step 1: Visit homepage (usually less protected)
home_response = session.get("https://target-site.com/")
print(f"Homepage status: {home_response.status_code}")
# Step 2: Visit a simple page to accumulate cookies
about_response = session.get("https://target-site.com/about")
print(f"About page status: {about_response.status_code}")
# Step 3: Now try the protected page
target_response = session.get("https://target-site.com/protected-page")
print(f"Target status: {target_response.status_code}")Automated Error Handling
Build resilience into your scraper with proper error handling:
import time
import random
class CloudflareHandler:
def __init__(self, proxy_provider):
self.session = requests.Session()
self.proxy_provider = proxy_provider
self.max_retries = 5
def fetch(self, url):
for attempt in range(self.max_retries):
proxy = self.proxy_provider.get_proxy()
self.session.proxies = {"http": proxy, "https": proxy}
try:
response = self.session.get(url, headers=headers, timeout=20)
if response.status_code == 200:
return response
if response.status_code == 403:
if "1020" in response.text:
print(f"Error 1020 on attempt {attempt + 1}")
# Rotate proxy and add delay
time.sleep(random.uniform(2, 5))
continue
if response.status_code == 429:
wait = int(response.headers.get("Retry-After", 30))
print(f"Rate limited, waiting {wait}s")
time.sleep(wait)
continue
except requests.exceptions.Timeout:
continue
return NonePrevention: Avoiding Error 1020 Proactively
Rather than fixing 1020 errors after they occur, prevent them:
- Start slow: Begin with 1-2 requests per minute and gradually increase
- Respect robots.txt: While not legally binding, sites that see robots.txt compliance are less likely to block you
- Rotate everything: IPs, User-Agents, and even request patterns
- Add realistic delays: Real users don’t send requests every 100ms
- Maintain sessions: Reuse cookies within a session window
For comprehensive scraping best practices, see our guides on rate limiting and user-agent rotation.
Conclusion
Cloudflare Error 1020 is a firewall-level block that requires a different approach than JavaScript challenges. Start with residential proxies and proper headers (Fixes 1-3), which resolve the majority of cases. Escalate to TLS fingerprint matching or browser automation only when needed.
The key insight is that 1020 errors are usually caused by who you are (your IP) or how you look (your headers and fingerprint), not what you do. Fix those two things, and most 1020 blocks disappear.
For broader Cloudflare bypass strategies, read our comprehensive Cloudflare bypass guide.
- 403 Forbidden in Web Scraping: How to Fix It
- Best CAPTCHA Solving Services in 2026: Complete Comparison
- Anti-Phishing with Proxies: How Security Teams Use Mobile IPs
- Brand Protection with Proxies: Detect Counterfeit Sellers & Trademark Violations
- How Cybersecurity Teams Use Proxies for Threat Intelligence
- Using Mobile Proxies for Dark Web Monitoring and Research
- 403 Forbidden in Web Scraping: How to Fix It
- Best CAPTCHA Solving Services in 2026: Complete Comparison
- Anti-Phishing with Proxies: How Security Teams Use Mobile IPs
- Brand Protection with Proxies: Detect Counterfeit Sellers & Trademark Violations
- How Cybersecurity Teams Use Proxies for Threat Intelligence
- Using Mobile Proxies for Dark Web Monitoring and Research
- 403 Forbidden in Web Scraping: How to Fix It
- Best CAPTCHA Solving Services in 2026: Complete Comparison
- Anti-Phishing with Proxies: How Security Teams Use Mobile IPs
- Brand Protection with Proxies: Detect Counterfeit Sellers & Trademark Violations
- How Cybersecurity Teams Use Proxies for Threat Intelligence
- Using Mobile Proxies for Dark Web Monitoring and Research
Related Reading
- 403 Forbidden in Web Scraping: How to Fix It
- Best CAPTCHA Solving Services in 2026: Complete Comparison
- Anti-Phishing with Proxies: How Security Teams Use Mobile IPs
- Brand Protection with Proxies: Detect Counterfeit Sellers & Trademark Violations
- How Cybersecurity Teams Use Proxies for Threat Intelligence
- Using Mobile Proxies for Dark Web Monitoring and Research