# Guía de Proxy curl_cffi: Configuración, Autenticación, Rotación y Async

curl_cffi te ofrece un apretón de manos TLS con forma de navegador. Un proxy te proporciona una IP de salida limpia. Aquí te explicamos exactamente cómo conectar ambos — y por qué el prefijo https:// en tu diccionario de proxies está generando ErrCode 35.

curl_cffi es el enlace de Python a un fork de curl-impersonate: reproduce las huellas digitales TLS/JA3 y HTTP/2 de un navegador real en lugar de anunciarse como urllib3. Eso soluciona un eje de bloqueo. El otro es la IP de salida, que es donde entra un proxy curl_cffi — y donde la documentación se vuelve escasa. La sección oficial de proxy tiene unas quince líneas, y un problema de GitHub de febrero de 2023 todavía está entre los cinco primeros para este tema. Esta guía cubre toda la superficie: el parámetro `proxy`, el diccionario estilo requests y sus nombres de claves reales, `proxy_auth`, sesiones, rotación por solicitud, async, SOCKS5 — y las cadenas de error exactas que pegarás en un cuadro de búsqueda.

## Sintaxis de proxy curl_cffi: preferir proxy= sobre el diccionario proxies

curl_cffi acepta dos formas. La nativa es una sola cadena `proxy=`, añadida en la v0.6.0; el diccionario `proxies=` existe para compatibilidad con requests, y la documentación recomienda el único parámetro a menos que realmente necesites diferentes proxies por esquema. Internamente colapsan a lo mismo — `proxy="..."` se convierte en `{"all": "..."}` — y ambos funcionan en los ayudantes del módulo, en `Session`, en `AsyncSession` y en solicitudes individuales.

```python
# pip install curl_cffi --upgrade    (Python 3.10+ since v0.14)
import curl_cffi

PROXY = "http://USER:PASS@residentialpremium.quantumproxies.io:8080"

# Native form — one string, applies to every scheme
r = curl_cffi.get(
    "https://tls.browserleaks.com/json",
    impersonate="chrome",
    proxy=PROXY,
    timeout=30,
)
print(r.status_code, r.json()["ja3n_hash"])

# requests-compatible form
r = curl_cffi.get(
    "https://httpbin.org/ip",
    impersonate="chrome",
    proxies={"http": PROXY, "https": PROXY},
    timeout=30,
)
print(r.json())  # {'origin': '<proxy exit IP>'}
```

Cuatro cosas sobre ese diccionario que vale la pena saber, porque ninguna de ellas es obvia desde el README:

- **Las claves válidas son `all`, `http`, `https`, `ws` y `wss`.** `all` es el comodín; las claves de websocket solo importan para el cliente WebSocket.
- **Las claves por host también funcionan.** `https://api.example.com` o `all://example.com` enrutan solo ese host a través de un proxy dado — útil para enviar un dominio difícil a través de IPs residenciales y dejar el resto directo.
- **No puedes pasar ambos.** `proxy=` más `proxies=` en la misma llamada genera `TypeError: Cannot specify both 'proxy' and 'proxies'`, y la misma verificación se ejecuta a nivel de sesión.
- **Se respetan las variables de entorno** — `http_proxy`, `https_proxy`, `ws_proxy`, `wss_proxy`. Pasa `trust_env=False` a una `Session` cuando una variable corporativa secuestra tu scraper.

Una nota sobre las importaciones: desde la v0.10.0 el paquete es invocable directamente (`curl_cffi.get`, `curl_cffi.Session`). Los tutoriales más antiguos usan `from curl_cffi import requests`, lo cual aún funciona pero se lee mal junto a la biblioteca requests real — y explica por qué la mitad de los fragmentos en línea parecen de un proyecto diferente.

## La trampa https://: ErrCode 35 y WRONG_VERSION_NUMBER

Este único error genera más preguntas sobre proxy curl_cffi que todo lo demás combinado. El problema #6 en el rastreador de proyectos — abierto y cerrado el mismo día en febrero de 2023 — todavía está en la primera página, porque el error que produce parece un error de TLS en lugar de un error de configuración:

```python
# WRONG: this asks curl to open a TLS connection *to the proxy itself*
proxies = {"https": "https://USER:PASS@residentialpremium.quantumproxies.io:8080"}

# Failed to perform, ErrCode: 35, Reason:
# 'error:100000f7:SSL routines:OPENSSL_internal:WRONG_VERSION_NUMBER'

# RIGHT: plain HTTP CONNECT, then the TLS tunnel runs through to the target
proxies = {"https": "http://USER:PASS@residentialpremium.quantumproxies.io:8080"}

# Or skip the dict entirely
proxy = "http://USER:PASS@residentialpremium.quantumproxies.io:8080"
```

La clave nombra el protocolo del *objetivo*; el valor nombra cómo llegas al *proxy*. Un proxy HTTPS sobre HTTP normal toma un `CONNECT` en texto plano, luego tunela tu tráfico cifrado sin tocarlo — por lo que la URL del proxy comienza con `http://` incluso cuando cada URL que obtienes es HTTPS. Los proxies HTTPS sobre HTTPS existen pero son raros y deben ser explícitamente soportados por la puerta de enlace. Requests formula el mismo fallo de manera mucho más útil — *tu proxy parece usar solo HTTP y no HTTPS* — por lo que una configuración idéntica puede parecer un error específico de curl_cffi. Las versiones recientes advierten y enlazan el problema #6, pero es solo una advertencia: la solicitud aún falla.

## Autenticación: credenciales en la URL o proxy_auth

Las puertas de enlace autenticadas aceptan la forma incrustada habitual, `http://USER:PASS@host:port`, con la trampa habitual: un `@`, `:` o `/` sin escapar en la contraseña divide la URL en el lugar equivocado y produce un fallo de autenticación que parece un proxy muerto. curl_cffi ofrece una salida que requests no tiene — una tupla `proxy_auth` pasada a libcurl como opciones de nombre de usuario y contraseña separadas, por lo que no se involucra ninguna codificación.

```python
import curl_cffi
from urllib.parse import quote

# Option A — credentials in the URL, password URL-encoded
pw = quote("p@ss:word", safe="")
r = curl_cffi.get(
    "https://httpbin.org/ip",
    proxy=f"http://USER:{pw}@residentialpremium.quantumproxies.io:8080",
    impersonate="chrome",
    timeout=30,
)

# Option B — keep credentials out of the URL entirely
r = curl_cffi.get(
    "https://httpbin.org/ip",
    proxy="http://residentialpremium.quantumproxies.io:8080",
    proxy_auth=("USER", "PASS"),
    impersonate="chrome",
    timeout=30,
)
print(r.json())
```

Una tercera opción elimina por completo esta clase de error: la lista blanca de IP. Cada [plan residencial de QuantumProxies](https://quantumproxies.io/residential-proxies) te permite autorizar la IP de tu servidor en lugar de enviar usuario:contraseña, por lo que la URL del proxy se convierte en un simple `http://residentialboson.quantumproxies.io:PORT` — nada que codificar, ningún secreto en tu árbol de código fuente. Si las credenciales en sí están siendo rechazadas, nuestra guía sobre [todas las causas de 407 Proxy Authentication Required](https://quantumproxies.io/blog/fix-407-proxy-authentication-required) cubre el resto.

![Una solicitud de curl_cffi con un apretón de manos TLS que suplanta a Chrome, pasando por una puerta de enlace de proxy rotativa: un CONNECT en texto plano abre un túnel, el apretón de manos pasa intacto y el objetivo ve el ClientHello y los ajustes HTTP/2 de Chrome llegando desde una IP de salida residencial limpia.](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785776553/blog/generated/qp-b10-006-diag1.png)
*El proxy tunela el apretón de manos en lugar de terminarlo, por lo que la suplantación del navegador sobrevive al salto y el objetivo ve una IP de salida limpia.*

## Sesiones, cookies y el detalle de reutilización de credenciales

Una `Session` mantiene cookies, agrupación de conexiones y tus valores predeterminados en un solo lugar, que es lo que deseas para cualquier cosa de varios pasos. Establece `impersonate` y `proxy` una vez y cada solicitud los hereda:

```python
from curl_cffi import Session

with Session(
    impersonate="chrome",
    proxy="http://USER-sid-a1b2-ttl-30m:PASS@residentialpremium.quantumproxies.io:8080",
    timeout=30,
    retry=3,
) as s:
    s.get("https://httpbin.org/cookies/set/foo/bar")
    r = s.get("https://httpbin.org/cookies")
    print(r.json(), s.cookies.get_dict())
```

Dos comportamientos merecen mención. Primero, siempre que se configura un proxy, curl_cffi activa la opción de no reutilización de credenciales de proxy de libcurl: se fuerza una nueva conexión cuando cambia el nombre de usuario del proxy, y la caché de sesión TLS se basa en la dirección del proxy, por lo que una IP de salida anterior no puede filtrarse en una solicitud posterior a través de una sesión reutilizada. Si codificas identificadores de sesión persistente en el nombre de usuario, como hacen la mayoría de las puertas de enlace rotativas, obtienes ese aislamiento gratis. Segundo, `retry` (un int, o una `RetryStrategy` de `curl_cffi.requests` con retraso, retroceso y variación) solo se vuelve a ejecutar en una excepción de transporte. No **reintenta** un 403 o 429 como lo hace el `status_forcelist` de urllib3 — ese bucle aún es tuyo para escribir. Los documentos de compatibilidad enumeran los reintentos como no soportados, lo cual está desactualizado: el parámetro llegó en la v0.15.0.

[Apunta curl_cffi a una puerta de enlace residencial rotativa](https://quantumproxies.io/rotating-proxies)

## Rotación por solicitud y async

curl_cffi anuncia asyncio con rotación de proxy en cada solicitud, y eso es literal: un argumento `proxy=` en una llamada individual anula lo que la sesión mantiene. Rara vez necesitas una lista de proxies para explotarlo — una puerta de enlace rotativa asigna un servidor de salida fresco del lado del servidor en cada conexión, por lo que un solo endpoint más concurrencia ya es rotación. Donde sí quieres control (una IP estable por trabajador, por cuenta, por carrito) coloca un token de sesión en el nombre de usuario y deja que la puerta de enlace fije esa salida.

```python
import asyncio
from curl_cffi import AsyncSession

GATE = "residentialpremium.quantumproxies.io:8080"
URLS = ["https://httpbin.org/ip"] * 20

async def fetch(session, url, worker):
    # one sticky exit IP per worker; drop the -sid-...-ttl-30m suffix for full rotation
    proxy = f"http://USER-sid-{worker}-ttl-30m:PASS@{GATE}"
    r = await session.get(url, proxy=proxy, timeout=30)
    return r.status_code, r.json()["origin"]

async def main():
    async with AsyncSession(impersonate="chrome", max_clients=10) as s:
        return await asyncio.gather(
            *(fetch(s, u, i % 5) for i, u in enumerate(URLS))
        )

for status, ip in asyncio.run(main()):
    print(status, ip)
```

`max_clients` limita los manejadores de curl concurrentes en el pool (10 por defecto), por lo que es tu dial de concurrencia real — añadir un semáforo a un gather sin límites es el error habitual. La misma lógica de dimensionamiento se aplica a cualquier cliente async, que cubrimos en [scraping async en Python con httpx y aiohttp](https://quantumproxies.io/blog/python-async-scraping-httpx-aiohttp-proxies). Si rotar por solicitud o fijar una sesión depende de si el sitio rastrea el estado a través de solicitudes; los compromisos están en [sesiones persistentes vs proxies rotativos](https://quantumproxies.io/blog/sticky-sessions-vs-rotating-proxies).

![El error de proxy en curl_cffi detrás de ErrCode 35: una URL de proxy https:// hace que curl abra TLS hacia el propio proxy y falle con WRONG_VERSION_NUMBER, mientras que http:// envía un CONNECT simple y tuneliza TLS hasta el objetivo. La tarjeta contrasta configuraciones que funcionan con los errores habituales, como contraseñas sin escapar.](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785776553/blog/generated/qp-b10-006-diag2.png)
*La mayoría de los fallos de proxy curl_cffi son una de cinco cosas — y cuatro de ellas son un solo carácter en una cadena.*

## SOCKS5, HTTP/3 y los interruptores de seguridad

SOCKS no necesita instalación extra — libcurl está compilado, por lo que a diferencia de requests no hay un `[socks]` extra para recordar. Usa `socks5h://USER:PASS@residentialpremium.quantumproxies.io:1080`: el `h` empuja la resolución DNS al proxy, lo que detiene las filtraciones desde tu propia red y resuelve nombres de host geocercados desde la ubicación de la salida. curl_cffi detecta el prefijo `socks` y omite la bandera de tunelización HTTP, ya que el protocolo SOCKS maneja eso por sí mismo. Cada plan aquí expone endpoints HTTP y [SOCKS5](https://quantumproxies.io/socks5-proxies) en la misma puerta de enlace, por lo que cambiar es un intercambio de esquema en lugar de un nuevo pedido.

- **HTTP/3 sobre un proxy** llegó en la v0.15.0 junto con las huellas digitales de http/3, pero necesita un servidor SOCKS5 que hable UDP, no una puerta de enlace HTTP simple. Específico hasta que tu objetivo recompense QUIC.
- **Endurecimiento SSRF.** La misma versión incluyó un aviso: si obtienes URLs suministradas por otras personas, las redirecciones pueden ser llevadas a tu red interna. Establece `allow_redirects="safe"`, o desactiva las redirecciones.
- **Depuración.** La v0.15 lanzó una CLI: `curl-cffi get tls.browserleaks.com/json --impersonate chrome` te dice en una línea si la suplantación está funcionando, antes de culpar al proxy.

## Cuando curl_cffi más un proxy es suficiente

Más a menudo de lo que la gente espera. Si el objetivo sirve JSON desde una API interna o HTML renderizado por el servidor, y el único obstáculo es una verificación de huellas digitales, un apretón de manos igualado más una salida residencial lo soluciona a una fracción del costo y latencia de un navegador. Las preguntas frecuentes del proyecto son directas sobre el techo: las huellas digitales son un factor entre varios, junto con la calidad de la IP, la tasa de solicitudes y las verificaciones de JavaScript, y los niveles de protección más altos necesitan tanto un mejor pool de proxies como una automatización real del navegador. Cuando la suplantación está configurada correctamente y aún estás bloqueado, la variable restante casi siempre es la IP de salida — aislar eso en cinco minutos es el tema de [curl_cffi vs requests](https://quantumproxies.io/blog/curl-cffi-vs-requests). Si prefieres no ejecutar ninguno, la [Scraper API](https://quantumproxies.io/scraper-api) maneja huellas digitales, proxies y renderizado JS opcional detrás de una sola llamada.

## Preguntas frecuentes

### ¿Cómo uso un proxy con curl_cffi?

Pasa `proxy="http://USER:PASS@host:port"` a cualquier método de solicitud, sesión o sesión async. El diccionario estilo requests `proxies={"http": ..., "https": ...}` también funciona, pero el proyecto recomienda el único parámetro a menos que necesites diferentes proxies por esquema. Pasar ambos genera un TypeError.

### ¿Por qué curl_cffi lanza ErrCode 35 WRONG_VERSION_NUMBER?

Porque la URL del proxy comienza con `https://`. Un proxy estándar espera una solicitud CONNECT en texto plano y luego tunela tu TLS; un prefijo `https://` hace que curl intente hacer un apretón de manos TLS con el proxy mismo, que responde en HTTP plano. Cambia el valor a `http://` — la clave `https` se refiere al objetivo, no al salto.

### ¿curl_cffi soporta proxies SOCKS5?

Sí, de forma nativa — libcurl está incluido, por lo que no hay un extra opcional para instalar. Usa el esquema `socks5h://` para que los nombres de host sean resueltos por el proxy en lugar de tu máquina. SOCKS4, SOCKS4a y `socks5://` simple también son aceptados; la biblioteca omite la tunelización HTTP para cualquier proxy cuyo esquema comience con socks.

### ¿curl_cffi puede rotar proxies en cada solicitud?

Sí. Un argumento `proxy=` en una llamada individual anula el valor predeterminado de la sesión, incluso dentro de una `AsyncSession`, que es lo que el README significa por asyncio con rotación por solicitud. Con una puerta de enlace rotativa a menudo no necesitas ninguna lógica: el mismo endpoint entrega una IP de salida diferente por conexión.

### ¿curl_cffi puede eludir Cloudflare?

A veces. Elimina la señal de huella digital TLS y HTTP/2, lo cual es suficiente para niveles de protección básicos. No puede ejecutar desafíos de JavaScript, resolver Turnstile, o reparar una IP de centro de datos que una base de datos de reputación ya ha marcado. Trata la suplantación como uno de tres requisitos, no la respuesta.

Toda la configuración es más pequeña de lo que su reputación sugiere: una cadena `proxy`, `impersonate` configurada una vez en la sesión, un tiempo de espera en cada llamada, y credenciales ya sea codificadas en URL o pasadas como una tupla `proxy_auth`. Hazlo bien y la variable restante es la calidad de la IP — un apretón de manos perfecto de Chrome desde una dirección de centro de datos marcada sigue siendo una dirección de centro de datos marcada. Nuestro desglose de [huellas digitales JA3 y JA4](https://quantumproxies.io/blog/tls-ja3-fingerprinting-scraping) explica por qué las dos verificaciones son independientes.

[Obtén IPs residenciales que coincidan con tu suplantación](https://quantumproxies.io/residential-proxies)
