7 Proven Ways to Bypass Cloudflare Detection When Using Proxies
Cloudflare processes over 20% of all web traffic and employs a multi-layered bot protection system. When using proxy servers, the likelihood of encountering a CAPTCHA or being blocked increases significantly. In this guide, we will discuss the technical aspects of detection and practical bypass methods that work in 2024.
How Cloudflare Identifies Proxies and Bots
Cloudflare uses a comprehensive analysis system that checks dozens of parameters for each request. Understanding the detection mechanisms is the first step to successfully bypassing protection.
Main Detection Methods
TLS Fingerprinting: Cloudflare analyzes the parameters of the SSL/TLS handshake (cipher suites, extensions, order of their occurrence). Each HTTP client has a unique "fingerprint." For example, Python requests use OpenSSL with a characteristic set of ciphers that can be easily distinguished from Chrome or Firefox.
When analyzing a request, Cloudflare matches the TLS fingerprint with the declared User-Agent. If you specify Chrome 120, but the TLS parameters correspond to Python requests — this is an instant bot detection.
| Check Parameter | What is Analyzed | Detection Risk |
|---|---|---|
| TLS fingerprint | Cipher suites, extensions, TLS version | High |
| HTTP/2 fingerprint | Header order, SETTINGS frames | High |
| IP Reputation | IP history, data center affiliation | Medium |
| JavaScript challenge | JS execution, canvas fingerprint, WebGL | High |
| Behavioral Analysis | Request patterns, timing, mouse movements | Medium |
Since 2023, Cloudflare has been actively using machine learning to analyze behavioral patterns. The system tracks not only technical parameters but also the time intervals between requests, the sequence of user actions, mouse movements, and page scrolling.
Masking TLS Fingerprint
TLS fingerprinting is the most effective method for detecting bots. Standard HTTP clients (requests, curl, axios) create a fingerprint that cannot be confused with a real browser. The solution is to use specialized libraries that mimic browser TLS behavior.
Using curl-impersonate
The curl-impersonate library is a modified version of curl that accurately replicates the TLS and HTTP/2 fingerprints of popular browsers. It supports Chrome, Firefox, Safari, and Edge.
# Installing curl-impersonate
git clone https://github.com/lwthiker/curl-impersonate
cd curl-impersonate
make chrome-build
# Using with Chrome 120 emulation
curl_chrome120 -x http://username:password@proxy.example.com:8080 \
-H "Accept-Language: en-US,en;q=0.9" \
https://example.com
Python: tls-client Library
For Python, there is a wrapper called tls-client that uses curl-impersonate under the hood and provides an interface similar to requests.
import tls_client
# Creating a session with Chrome 120 fingerprint
session = tls_client.Session(
client_identifier="chrome_120",
random_tls_extension_order=True
)
# Setting up the proxy
proxies = {
'http': 'http://username:password@proxy.example.com:8080',
'https': 'http://username:password@proxy.example.com:8080'
}
# Making a request
response = session.get(
'https://example.com',
proxies=proxies,
headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/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',
'DNT': '1',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1'
}
)
print(response.status_code)
Important: When using tls-client, it is critically important that the User-Agent in the headers matches the selected client_identifier. Mismatches will lead to immediate detection.
Checking TLS Fingerprint
Before starting scraping, it is advisable to check your TLS fingerprint. Use services like tls.peet.ws or ja3er.com for analysis.
# Checking fingerprint
response = session.get('https://tls.peet.ws/api/all')
print(response.json()['tls']['ja3'])
# Compare with the fingerprint of real Chrome:
# https://kawayiyi.com/tls-fingerprint-database/
Proper HTTP Header Configuration
Even with the correct TLS fingerprint, incorrect HTTP headers will reveal a bot. Cloudflare analyzes not only the presence of headers but also their order, value format, and logical consistency.
Mandatory Headers for Chrome
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.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',
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br',
'DNT': '1',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'none',
'Sec-Fetch-User': '?1',
'Sec-Ch-Ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
'Sec-Ch-Ua-Mobile': '?0',
'Sec-Ch-Ua-Platform': '"Windows"',
'Cache-Control': 'max-age=0'
}
The headers Sec-Ch-Ua-* appeared in Chrome 89 and are part of the Client Hints API. Their absence when using a modern User-Agent is a clear sign of a bot.
Header Order Matters
In HTTP/2, the order of headers is fixed for each browser. Python requests and other standard clients send headers in alphabetical order, which differs from browser behavior. Use libraries that support custom header order.
Tip: Use the browser's DevTools (Network tab → right-click on the request → Copy → Copy as cURL) to get an exact copy of the headers from a real browser. Then adapt them to your code.
Dynamic User-Agent Generation
Using the same User-Agent for all requests increases the risk of detection. Create a pool of relevant User-Agents and rotate them.
import random
# Pool of relevant User-Agents (December 2024)
USER_AGENTS = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
]
def get_random_headers():
ua = random.choice(USER_AGENTS)
# Adapting other headers to the selected UA
if 'Chrome' in ua:
return {
'User-Agent': ua,
'Sec-Ch-Ua': '"Not_A Brand";v="8", "Chromium";v="120"',
# ... other Chrome headers
}
elif 'Firefox' in ua:
return {
'User-Agent': ua,
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
# ... Firefox headers
}
# ... handling other browsers
Using Headless Browsers
When Cloudflare uses a JavaScript challenge or advanced detection, the only reliable way to bypass it is with a real browser. Headless browsers automatically handle JavaScript, cookies, and create a fully authentic fingerprint.
Playwright with Anti-Detect Patches
Playwright is a modern alternative to Selenium with better performance. However, standard Playwright is easily detected through navigator.webdriver and other markers. Use playwright-stealth for masking.
from playwright.sync_api import sync_playwright
from playwright_stealth import stealth_sync
def bypass_cloudflare(url, proxy):
with sync_playwright() as p:
browser = p.chromium.launch(
headless=True,
proxy={
"server": f"http://{proxy['host']}:{proxy['port']}",
"username": proxy['username'],
"password": proxy['password']
},
args=[
'--disable-blink-features=AutomationControlled',
'--disable-dev-shm-usage',
'--no-sandbox'
]
)
context = browser.new_context(
viewport={'width': 1920, 'height': 1080},
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
locale='en-US',
timezone_id='America/New_York'
)
page = context.new_page()
stealth_sync(page) # Applying anti-detect patches
# Navigating to the page
page.goto(url, wait_until='networkidle', timeout=30000)
# Waiting for Cloudflare challenge to pass (usually 5-10 seconds)
page.wait_for_timeout(8000)
# Checking for successful bypass
if 'Just a moment' in page.content():
print('Cloudflare challenge not passed')
return None
# Extracting cookies for further use
cookies = context.cookies()
html = page.content()
browser.close()
return {'html': html, 'cookies': cookies}
# Usage
proxy_config = {
'host': 'proxy.example.com',
'port': 8080,
'username': 'user',
'password': 'pass'
}
result = bypass_cloudflare('https://example.com', proxy_config)
Puppeteer Extra with Plugins
For the Node.js ecosystem, the best solution is puppeteer-extra with the puppeteer-extra-plugin-stealth. This plugin applies over 30 different automation masking techniques.
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());
async function bypassCloudflare(url, proxyUrl) {
const browser = await puppeteer.launch({
headless: 'new',
args: [
`--proxy-server=${proxyUrl}`,
'--disable-blink-features=AutomationControlled',
'--window-size=1920,1080'
]
});
const page = await browser.newPage();
// Setting viewport and user-agent
await page.setViewport({ width: 1920, height: 1080 });
await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');
// Overriding navigator.webdriver
await page.evaluateOnNewDocument(() => {
delete Object.getPrototypeOf(navigator).webdriver;
});
// Navigating to the page
await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
// Waiting for challenge to pass
await page.waitForTimeout(8000);
// Getting content and cookies
const content = await page.content();
const cookies = await page.cookies();
await browser.close();
return { content, cookies };
}
// Example usage
bypassCloudflare('https://example.com', 'http://user:pass@proxy.example.com:8080')
.then(result => console.log('Success'))
.catch(err => console.error(err));
Performance: Headless browsers consume significantly more resources (200-500 MB RAM per instance). For high-load tasks, use them only to obtain cookies, then switch to HTTP clients with those cookies.
Choosing the Right Proxy Type for Bypassing Cloudflare
The type of proxy critically affects the success of the bypass. Cloudflare maintains databases of data center IP addresses and applies stricter verification rules to them.
| Proxy Type | Bypass Probability | Speed | Cost | Recommendation |
|---|---|---|---|---|
| Datacenter | 30-40% | High | Low | Only with headless browsers |
| Residential | 85-95% | Medium | High | Optimal choice |
| Mobile | 90-98% | Medium | Very high | For critical tasks |
| ISP (Static Residential) | 80-90% | High | Medium | Balance of price and quality |
Why Residential Proxies Are More Effective
Residential proxies use IP addresses of real devices (home routers, smartphones). Cloudflare cannot block such IPs en masse, as it would block regular users. Statistics show that residential IPs receive CAPTCHAs 15-20 times less often than data centers.
When working with residential proxies, geolocation is critically important. If the target site is aimed at the US, using proxies from Asia will raise suspicion. Choose providers with a wide geographical reach and the ability to target by cities.
Mobile Proxies for Maximum Reliability
Mobile proxies use IP addresses from mobile operators (4G/5G). A feature of mobile networks is the dynamic IP change through airplane mode, which provides an almost unlimited number of clean IP addresses. The likelihood of blocking a mobile IP is close to zero.
# Example of rotating mobile IP via API
import requests
import time
def rotate_mobile_ip(proxy_api_url):
"""Changing mobile proxy IP"""
response = requests.get(f"{proxy_api_url}/rotate")
if response.status_code == 200:
print("IP successfully changed")
time.sleep(5) # Waiting for changes to take effect
return True
return False
# Usage with mobile proxy
mobile_proxy = "http://user:pass@mobile.proxy.com:8080"
for i in range(10):
# Making a request
response = requests.get(
'https://example.com',
proxies={'http': mobile_proxy, 'https': mobile_proxy}
)
# Rotating IP after each request
rotate_mobile_ip('https://api.proxy.com/mobile')
Managing Cookies and Sessions
After successfully passing the Cloudflare challenge, the server sets cookies (cf_clearance, __cfduid, and others) that confirm the legitimacy of the client. Proper management of these cookies allows avoiding repeated checks.
Extracting and Reusing cf_clearance
The cf_clearance cookie is typically valid for 30-60 minutes. After obtaining it through a headless browser, it can be used in regular HTTP requests.
import requests
import pickle
from datetime import datetime, timedelta
class CloudflareCookieManager:
def __init__(self, cookie_file='cf_cookies.pkl'):
self.cookie_file = cookie_file
self.cookies = self.load_cookies()
def load_cookies(self):
"""Loading saved cookies"""
try:
with open(self.cookie_file, 'rb') as f:
data = pickle.load(f)
# Checking expiration
if data['expires'] > datetime.now():
return data['cookies']
except FileNotFoundError:
pass
return None
def save_cookies(self, cookies, ttl_minutes=30):
"""Saving cookies with TTL"""
data = {
'cookies': cookies,
'expires': datetime.now() + timedelta(minutes=ttl_minutes)
}
with open(self.cookie_file, 'wb') as f:
pickle.dump(data, f)
def get_cf_clearance(self, url, proxy):
"""Getting cf_clearance through the browser"""
if self.cookies:
return self.cookies
# Here is the code to launch the browser (from the previous section)
# ...
browser_cookies = bypass_cloudflare(url, proxy)['cookies']
# Converting to requests format
cookies_dict = {c['name']: c['value'] for c in browser_cookies}
self.save_cookies(cookies_dict)
self.cookies = cookies_dict
return cookies_dict
def make_request(self, url, proxy):
"""Request with automatic cookie management"""
cookies = self.get_cf_clearance(url, proxy)
response = requests.get(
url,
cookies=cookies,
proxies={'http': proxy, 'https': proxy},
headers=get_random_headers()
)
# If we received a challenge again — update cookies
if response.status_code == 403 or 'cf-browser-verification' in response.text:
print("Cookies expired, obtaining new ones...")
self.cookies = None
return self.make_request(url, proxy)
return response
# Usage
manager = CloudflareCookieManager()
response = manager.make_request(
'https://example.com/api/data',
'http://user:pass@proxy.example.com:8080'
)
Binding Cookies to IP Address
Cloudflare binds cf_clearance to the IP address from which the challenge was passed. Using this cookie from a different IP will lead to blocking. When working with rotating proxies, it is necessary to store a separate set of cookies for each IP.
import hashlib
class IPBoundCookieManager:
def __init__(self):
self.cookies_by_ip = {}
def get_ip_hash(self, proxy_url):
"""Creating a hash for proxy identification"""
return hashlib.md5(proxy_url.encode()).hexdigest()
def get_cookies_for_proxy(self, proxy_url, target_url):
"""Getting cookies for a specific proxy"""
ip_hash = self.get_ip_hash(proxy_url)
if ip_hash in self.cookies_by_ip:
cookies_data = self.cookies_by_ip[ip_hash]
if cookies_data['expires'] > datetime.now():
return cookies_data['cookies']
# Getting new cookies through the browser
new_cookies = self.fetch_cookies_with_browser(target_url, proxy_url)
self.cookies_by_ip[ip_hash] = {
'cookies': new_cookies,
'expires': datetime.now() + timedelta(minutes=30)
}
return new_cookies
Proxy Rotation and Request Rate Control
Even with the right technical stack, too high a request frequency from one IP triggers rate limiting. Cloudflare analyzes traffic patterns and detects abnormal activity.
Proxy Rotation Strategies
There are three main approaches to rotation: round-robin (sequential), random, and sticky sessions (session binding). For bypassing Cloudflare, the optimal strategy is sticky sessions with request limits per IP.
import time
import random
from collections import defaultdict
from datetime import datetime, timedelta
class SmartProxyRotator:
def __init__(self, proxy_list, max_requests_per_ip=20, cooldown_minutes=10):
self.proxy_list = proxy_list
self.max_requests_per_ip = max_requests_per_ip
self.cooldown_minutes = cooldown_minutes
# Usage counters
self.usage_count = defaultdict(int)
self.last_used = {}
self.cooldown_until = {}
def get_proxy(self):
"""Getting the next available proxy"""
available_proxies = []
for proxy in self.proxy_list:
# Checking cooldown
if proxy in self.cooldown_until:
if datetime.now() < self.cooldown_until[proxy]:
continue
else:
# Resetting counter after cooldown
self.usage_count[proxy] = 0
del self.cooldown_until[proxy]
# Checking request limit
if self.usage_count[proxy] < self.max_requests_per_ip:
available_proxies.append(proxy)
if not available_proxies:
# If all proxies are in cooldown — wait
wait_time = min(
(self.cooldown_until[p] - datetime.now()).total_seconds()
for p in self.cooldown_until
)
print(f"All proxies in cooldown. Waiting {wait_time:.0f} seconds...")
time.sleep(wait_time + 1)
return self.get_proxy()
# Choosing the proxy with the least usage
proxy = min(available_proxies, key=lambda p: self.usage_count[p])
self.usage_count[proxy] += 1
self.last_used[proxy] = datetime.now()
# Setting cooldown upon reaching limit
if self.usage_count[proxy] >= self.max_requests_per_ip:
self.cooldown_until[proxy] = datetime.now() + timedelta(
minutes=self.cooldown_minutes
)
print(f"Proxy {proxy} reached limit. Cooldown for {self.cooldown_minutes} minutes.")
return proxy
def add_delay(self):
"""Random delay between requests (human emulation)"""
delay = random.uniform(2, 5) # 2-5 seconds
time.sleep(delay)
# Usage
proxy_pool = [
'http://user:pass@proxy1.example.com:8080',
'http://user:pass@proxy2.example.com:8080',
'http://user:pass@proxy3.example.com:8080',
# ... up to 50-100 proxies for stable operation
]
rotator = SmartProxyRotator(
proxy_pool,
max_requests_per_ip=15, # Conservative value
cooldown_minutes=15
)
# Making requests
for i in range(1000):
proxy = rotator.get_proxy()
response = requests.get(
'https://example.com/page',
proxies={'http': proxy, 'https': proxy},
headers=get_random_headers()
)
print(f"Request {i+1}: {response.status_code}")
rotator.add_delay()
Adaptive Rate Limiting
A more advanced approach is dynamically adapting the request frequency based on server responses. If 429 errors or CAPTCHAs start to appear, automatically reduce the pace.
class AdaptiveRateLimiter:
def __init__(self, initial_delay=3.0):
self.delay = initial_delay
self.min_delay = 1.0
self.max_delay = 30.0
self.success_streak = 0
self.failure_streak = 0
def on_success(self):
"""Successful request — can speed up"""
self.success_streak += 1
self.failure_streak = 0
if self.success_streak >= 10:
# Decrease delay by 10%
self.delay = max(self.min_delay, self.delay * 0.9)
self.success_streak = 0
def on_failure(self, status_code):
"""Error — slow down"""
self.failure_streak += 1
self.success_streak = 0
if status_code == 429: # Rate limit
# Aggressive slowdown
self.delay = min(self.max_delay, self.delay * 2.0)
elif status_code == 403: # Possible blocking
self.delay = min(self.max_delay, self.delay * 1.5)
print(f"Delay increased to {self.delay:.2f}s")
def wait(self):
"""Waiting before the next request"""
# Adding randomness ±20%
actual_delay = self.delay * random.uniform(0.8, 1.2)
time.sleep(actual_delay)
Ready-Made Tools and Libraries for Bypassing
Developing your own solution from scratch takes time and expertise. There are ready-made tools that automate the process of bypassing Cloudflare.
cloudscraper (Python)
The cloudscraper library is a wrapper around requests that automatically solves JavaScript challenges. It works with basic protections but may struggle with advanced checks.
import cloudscraper
# Creating a scraper with proxy support
scraper = cloudscraper.create_scraper(
browser={
'browser': 'chrome',
'platform': 'windows',
'desktop': True
}
)
# Setting up the proxy
proxies = {
'http': 'http://user:pass@proxy.example.com:8080',
'https': 'http://user:pass@proxy.example.com:8080'
}
# Making a request
response = scraper.get('https://example.com', proxies=proxies)
if response.status_code == 200:
print("Successful bypass")
print(response.text)
else:
print(f"Error: {response.status_code}")
FlareSolverr (Universal)
FlareSolverr is a proxy server that runs a headless browser to solve Cloudflare challenges. It works through an HTTP API and supports any programming language.
# Running FlareSolverr via Docker
docker run -d \
--name=flaresolverr \
-p 8191:8191 \
-e LOG_LEVEL=info \
ghcr.io/flaresolverr/flaresolverr:latest
# Using from Python
import requests
def solve_cloudflare(url, proxy=None):
flaresolverr_url = "http://localhost:8191/v1"
payload = {
"cmd": "request.get",
"url": url,
"maxTimeout": 60000
}
if proxy:
payload["proxy"] = {
"url": proxy
}
response = requests.post(flaresolverr_url, json=payload)
result = response.json()
if result['status'] == 'ok':
return {
'html': result['solution']['response'],
'cookies': result['solution']['cookies'],
'user_agent': result['solution']['userAgent']
}
else:
raise Exception(f"FlareSolverr error: {result['message']}")
# Example usage
result = solve_cloudflare(
'https://example.com',
proxy='http://user:pass@proxy.example.com:8080'
)
print(result['html'])
undetected-chromedriver
A patched version of Selenium ChromeDriver that automatically applies numerous anti-detect techniques. Easier to use than Playwright but less flexible.
import undetected_chromedriver as uc
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
def bypass_with_uc(url, proxy):
options = uc.ChromeOptions()
options.add_argument(f'--proxy-server={proxy}')
options.add_argument('--disable-blink-features=AutomationControlled')
driver = uc.Chrome(options=options, version_main=120)
try:
driver.get(url)
# Waiting for Cloudflare challenge to disappear
WebDriverWait(driver, 20).until_not(
EC.presence_of_element_located((By.ID, "cf-spinner-please-wait"))
)
# Additional wait for reliability
time.sleep(3)
# Getting the result
html = driver.page_source
cookies = driver.get_cookies()
return {'html': html, 'cookies': cookies}
finally:
driver.quit()
# Usage
result = bypass_with_uc(
'https://example.com',
'http://user:pass@proxy.example.com:8080'
)
Combined Approach: The optimal strategy is to use a headless browser only for the initial cookie retrieval, then switch to HTTP clients (tls-client, cloudscraper) with those cookies. This provides a balance between reliability and performance.
Conclusion
Bypassing Cloudflare when using proxies requires a comprehensive approach: proper TLS fingerprint, authentic HTTP headers, quality proxies, and effective session management. Key recommendations:
- Use residential or mobile proxies instead of data centers
- Apply libraries with the correct TLS fingerprint (tls-client, curl-impersonate)
- For complex cases, use headless browsers with anti-detect patches
- Save and reuse cf_clearance cookies
- Rotate proxies considering rate limiting (no more than 15-20 requests per IP)
- Add random delays between requests (2-5 seconds)
Cloudflare's protection is constantly evolving, so it is important to regularly update tools and adapt strategies. Monitor changes in fingerprinting techniques and test solutions against current versions of protection.
For stable operation, it is recommended to use professional proxy services with a wide pool of IPs and automatic rotation.