# Proxies en curl_cffi: autenticación, error 407 y SOCKS

Source: https://ipvolt.com/es/guides/curl-cffi-proxy-setup
Markdown: https://ipvolt.com/es/guides/curl-cffi-proxy-setup.md
Language: es

[Inicio de ipvolt](https://ipvolt.com/es.md) / [Guías](https://ipvolt.com/es/guides.md) / Proxies en curl_cffi: autenticación, error 407 y SOCKS

Integración
Publicado: 2026-10-10
8 min de lectura
Por ipvolt

Configura proxies en curl_cffi 0.16.3: credenciales e impersonate, por qué un 407 en CONNECT lanza ConnectionError y no ProxyError, HTTP_PROXY y socks5h.

Pon las credenciales en la URL del proxy y pásala como `proxy`:

```python
from curl_cffi import requests

PROXY = "http://user:pass@proxy.example.com:8080"

with requests.Session(impersonate="chrome", proxy=PROXY) as session:
    response = session.get("https://example.com/")
    print(response.status_code)
```

Para detectar los fallos del proxy, captura `ProxyError` y `ConnectionError`, y comprueba si `status_code` es 407. En curl_cffi 0.16, un proxy que rechaza `CONNECT` lanza `ConnectionError`, no `ProxyError`.

En nuestro laboratorio esto autenticó peticiones `https://` y `http://` contra un proxy local que exige autenticación Basic. Todo lo que sigue se registró el 10 de octubre de 2026 con curl_cffi 0.16.3 (libcurl 8.21.0) en Python 3.14.4. `impersonate` elige el perfil de navegador del que curl_cffi copia los ajustes de TLS y HTTP/2. Todos los resultados fueron iguales con y sin él.

Tres detalles antes de los errores:

- **Usa `proxy` o `proxies`, no los dos.** `proxies={"http": PROXY, "https": PROXY}` funcionó igual en una petición y en una `Session`. La clave es el esquema de la URL que pides: con solo la clave `"https"`, una petición `http://` salió directa. Pasar los dos argumentos lanzó `TypeError: Cannot specify both 'proxy' and 'proxies'`.
- **Codifica la contraseña de la URL con porcentajes.** `p@ss:w/rd` funcionó como `p%40ss%3Aw%2Frd`. Sin codificar, la petición lanzó `ProxyError` con `curl: (5) Unsupported proxy syntax`. `proxy_auth=("user", "p@ss:w/rd")` junto a una URL de proxy sin credenciales también funcionó y no necesita codificación.
- **Escribe `http://` para un proxy HTTP, también con URL `https://`.** El esquema describe la conexión con el proxy. Una URL de proxy `https://` apuntada al proxy HTTP sin TLS del laboratorio lanzó `SSLError` con `curl: (35) TLS connect error` y `WRONG_VERSION_NUMBER`. [SSL WRONG_VERSION_NUMBER con proxy](/es/guides/fix-ssl-wrong-version-number-proxy) explica ese mensaje.

## CONNECT tunnel failed, response 407: ConnectionError, no ProxyError

```text
curl_cffi.requests.exceptions.ConnectionError: Failed to perform, curl: (7) CONNECT tunnel failed, response 407. See https://curl.se/libcurl/c/libcurl-errors.html first for more details.
```

Para una URL `https://`, curl_cffi envía primero `CONNECT` al proxy. El proxy del laboratorio respondió 407 y la petición lanzó este error. El mensaje fue idéntico sin credenciales y con una contraseña incorrecta.

- `isinstance(error, ProxyError)` es `False`. `ProxyError` y `ConnectionError` son clases hermanas bajo `RequestException`, así que `except ProxyError` no lo captura.
- `error.code` es 7.
- `error.response` es una `Response` cuyo `status_code` es 0, no 407. El estado solo está en el mensaje.
- El código 7 no es exclusivo de un 407. Cuando el proxy del laboratorio no pudo llegar al destino respondió 502, y el mensaje fue `curl: (7) CONNECT tunnel failed, response 502`. Sin nada escuchando en el puerto del proxy fue `curl: (7) Failed to connect to 127.0.0.1:8443 over proxy 127.0.0.1 after 0 ms: Could not connect to server`.

La clase depende del código de error de curl, y ese código cambió entre versiones. Ejecutamos la misma petición en doce de ellas:

| curl_cffi | libcurl | `CONNECT` rechazado | Fallo de SOCKS |
| --- | --- | --- | --- |
| 0.16.0 a 0.16.3 | 8.21.0 | `ConnectionError`, código curl 7 | `ProxyError`, código curl 97 |
| 0.7.4 a 0.15.0 | 8.7.0 a 8.15.0 | `ProxyError`, código curl 56 | `ProxyError`, código curl 97 |

Hasta la 0.15.0 el mensaje era `curl: (56) CONNECT tunnel failed, response 407`. La línea de comandos de curl pasó del código de salida 56 al 7 en la 8.18.0. [Corrige el error de proxy 407](/es/guides/fix-proxy-error-407) tiene esa tabla y el orden de comprobaciones para las credenciales.

Este bloque gestiona un `CONNECT` rechazado antes y después del cambio, un fallo de SOCKS y una respuesta 407:

```python
import re

from curl_cffi.requests.exceptions import ConnectionError, ProxyError

CONNECT_STATUS = re.compile(r"CONNECT tunnel failed, response (\d{3})")


class ProxyFailure(Exception):
    pass


def fetch(session, url):
    try:
        response = session.get(url)
    except ProxyError as error:
        raise ProxyFailure(f"proxy error, curl {error.code}") from error
    except ConnectionError as error:
        rejected = CONNECT_STATUS.search(str(error))
        if error.code == 7 and rejected:
            raise ProxyFailure(f"proxy answered CONNECT with {rejected[1]}") from error
        raise
    if response.status_code == 407:
        raise ProxyFailure("proxy answered 407")
    return response
```

En el laboratorio informó `proxy answered CONNECT with 407`, `proxy answered CONNECT with 502`, `proxy answered 407` para una URL `http://` y `proxy error, curl 97` para un fallo de SOCKS. Con el puerto del proxy cerrado volvió a lanzar el `ConnectionError`.

## 407 en URL http://: una respuesta, no una excepción

Para una URL `http://` no hay túnel. curl_cffi envía la URL completa al proxy en un `GET` normal, como hace desde la 0.7.0b7 ([issue #326](https://github.com/lexiforest/curl_cffi/issues/326)), y el 407 del proxy vuelve como respuesta.

No se lanzó ninguna excepción. `response.status_code` fue 407, `response.ok` fue `False` y las cabeceras traían `Proxy-Authenticate: Basic realm="lab"`. El código que solo captura excepciones trata esto como una petición correcta con el cuerpo vacío.

`response.raise_for_status()` lanzó `curl_cffi.requests.exceptions.HTTPError: HTTP Error 407: Proxy Authentication Required`. En esa excepción, `error.response.status_code` es 407.

## Variables de entorno: http_proxy se lee, HTTP_PROXY no

Sin proxy en el código, libcurl toma uno del entorno:

- Para una URL `http://`, `HTTP_PROXY` en mayúsculas se ignoró y la petición salió directa. Con `http_proxy` en minúsculas pasó por el proxy.
- Para una URL `https://` se usaron tanto `HTTPS_PROXY` como `https_proxy`. Con solo `http_proxy`, una petición `https://` salió directa.
- `ALL_PROXY` y `all_proxy` se aplicaron a los dos esquemas.
- `NO_PROXY=127.0.0.1` y `no_proxy` en minúsculas enviaron la petición directa.

libcurl documenta la excepción: las variables también se comprueban en mayúsculas, "except the http_proxy one which is only used lowercase" ([variables de entorno de libcurl](https://curl.se/libcurl/c/libcurl-env.html)). El motivo es CGI: un servidor convierte una cabecera de petición `Proxy:` en una variable `HTTP_PROXY` para el script ([Everything curl](https://everything.curl.dev/usingcurl/proxies/env.html)).

Scrapy lee `HTTP_PROXY` en mayúsculas ([Proxies en Scrapy 2.19](/es/guides/scrapy-proxy-setup)), y aiohttp no lee ninguna variable salvo que pases `trust_env=True` ([Proxies en aiohttp 3.14](/es/guides/aiohttp-proxy-setup)). [Variables de entorno de proxy](/es/guides/proxy-environment-variables) compara más clientes.

Dos resultados son fáciles de pasar por alto:

- **`NO_PROXY` anula el argumento `proxy`.** Con `NO_PROXY=127.0.0.1` definida, `requests.get(url, proxy=PROXY)` salió directa con los dos esquemas. Al añadir `curl_options={CurlOpt.NOPROXY: ""}` volvió a pasar por el proxy. Frente a `http_proxy` ganó el argumento.
- **`trust_env=False` no desactivó las variables.** Una `Session(trust_env=False)` siguió usando `http_proxy` y `HTTPS_PROXY`. `proxy=""` tampoco las desactivó.

Dos ajustes sí enviaron la petición directa con las variables definidas:

```python
from curl_cffi import CurlOpt, requests

requests.get("https://example.com/", proxies={"all": ""})
requests.get("https://example.com/", curl_options={CurlOpt.NOPROXY: "*"})
```

Los dos funcionan también como argumentos de `Session`. Una cadena de proxy vacía "explicitly disables the use of a proxy, even if there is an environment variable set for it" ([CURLOPT_PROXY](https://curl.se/libcurl/c/CURLOPT_PROXY.html)).

## SOCKS: socks5:// resuelve en local, socks5h:// envía el nombre de host

El laboratorio pidió `http://dnsprobe.example:8080/`, un nombre que solo existe en el archivo hosts como `127.0.0.2`, a través de un servidor SOCKS5 que registra cada destino:

- Con `socks5://127.0.0.1:1080` el servidor registró `CONNECT atyp=IPv4 dest=127.0.0.2:8080`. curl_cffi resolvió el nombre por su cuenta.
- Con `socks5h://127.0.0.1:1080` registró `CONNECT atyp=DOMAIN dest=dnsprobe.example:8080`. El proxy recibió el nombre de host.

Una URL `https://` dio los mismos dos resultados. Es la convención del propio curl ([Usar un proxy con curl](/es/guides/curl-proxy-setup)). [socks5 vs socks5h](/es/blog/socks5-vs-socks5h) enumera los clientes que no la siguen.

Un handshake SOCKS fallido es el caso que sí lanza `ProxyError`:

```text
curl_cffi.requests.exceptions.ProxyError: Failed to perform, curl: (97) cannot complete SOCKS5 connection to 127.0.0.1. (1). See https://curl.se/libcurl/c/libcurl-errors.html first for more details.
```

`error.code` es 97. El último número entre paréntesis es el código de respuesta que envió el servidor SOCKS: el del laboratorio respondió 1, fallo general. Cuando el puerto de destino estaba cerrado y el servidor respondió 5, el mensaje terminó en `(5)`.

Un esquema que no corresponde al puerto falla de otras maneras:

- `socks5://` apuntado al puerto del proxy HTTP lanzó `ProxyError` con `curl: (97) Received invalid version in initial SOCKS5 response.`
- `http://` apuntado al puerto SOCKS lanzó `ProxyError` con `curl: (56) Proxy CONNECT aborted` para una URL `https://`.
- Sin nada escuchando en el puerto SOCKS, el error fue otra vez el `ConnectionError` con código 7.

## AsyncSession: la misma configuración, los mismos errores

```python
import asyncio

from curl_cffi import AsyncSession

PROXY = "http://user:pass@proxy.example.com:8080"


async def main():
    async with AsyncSession(impersonate="chrome", proxy=PROXY) as session:
        response = await session.get("https://example.com/")
        print(response.status_code)


asyncio.run(main())
```

Cada caso asíncrono coincidió con su equivalente síncrono. Un `CONNECT` rechazado lanzó el mismo `ConnectionError` con código 7. Una URL `http://` devolvió el 407 como respuesta. Un fallo de SOCKS lanzó `ProxyError` con código 97. `socks5://` y `socks5h://` enviaron los mismos tipos de dirección, y `HTTP_PROXY` se ignoró mientras que `http_proxy` se usó.

## Reintentos: retry repite las peticiones POST

curl_cffi no reintenta por defecto. El laboratorio envió un `POST` a través del proxy a un destino que lee la petición y cierra la conexión sin responder. La llamada lanzó `ConnectionError` con `curl: (52) Empty reply from server`, y el destino había recibido un `POST`.

Con `requests.Session(proxy=PROXY, retry=1)` la misma llamada devolvió 201, y el destino había recibido el cuerpo dos veces. `retry` repitió el `POST` igual que repite un `GET`.

`retry=2` ante un `CONNECT` rechazado envió tres peticiones `CONNECT` y lanzó el mismo error, con `Session` y con `AsyncSession`. Un reintento no arregla unas credenciales incorrectas.

Deja `retry` en 0 para las peticiones que no es seguro repetir, o envía una clave de idempotencia. [Reintentos con proxy: una respuesta perdida, dos trabajos creados](/es/blog/proxy-retries-duplicate-jobs) muestra qué cambia una clave. Si quieres ayuda de diagnóstico dentro de un agente de programación, [Proxy Toolkit MCP](/mcp) acepta descripciones de errores saneadas. Mantén las credenciales fuera de sus argumentos.

## Cómo se probó

En Ubuntu 26.04.1 con Python 3.14.4 y curl_cffi 0.16.3 (libcurl 8.21.0-IMPERSONATE con BoringSSL), todo en direcciones de loopback: `python -m http.server` como destinos HTTP y HTTPS, tres proxies que exigen autenticación Basic y registran cada petición, un servidor SOCKS5 que registra cada destino y otro que responde a cada petición con el código 1, y un destino que pierde la respuesta al primer `POST`. `dnsprobe.example` solo existía en una copia privada del archivo hosts. Cada uno de los 86 casos se ejecutó en un intérprete nuevo con un entorno limpio. La comparación de versiones instaló doce versiones, de la 0.7.4 a la 0.16.3. No se usó ninguna cuenta de proxy.

El [archivo del laboratorio](https://ipvolt.com/downloads/curl-cffi-proxy-setup/curl-cffi-proxy-lab.zip) contiene el [ejecutor](https://ipvolt.com/downloads/curl-cffi-proxy-setup/run_lab.py), los [casos](https://ipvolt.com/downloads/curl-cffi-proxy-setup/cases.py), el [proxy](https://ipvolt.com/downloads/curl-cffi-proxy-setup/auth_proxy.py), el [servidor SOCKS5](https://ipvolt.com/downloads/curl-cffi-proxy-setup/socks5_server.py), el [README](https://ipvolt.com/downloads/curl-cffi-proxy-setup/README.md), los [resultados registrados](https://ipvolt.com/downloads/curl-cffi-proxy-setup/results.json) y la [comparación de versiones](https://ipvolt.com/downloads/curl-cffi-proxy-setup/versions.json).

No se probó: macOS y Windows, autenticación de proxy Digest y NTLM, proxies SOCKS con autenticación, proxies a los que se llega por TLS, HTTP/3 y WebSockets.

ipvolt es un servicio de proxies para desarrolladores que todavía no está abierto; [únete a la lista de acceso anticipado](https://ipvolt.com/#waitlist-closing) y recibirás un solo correo cuando abra.

## Fuentes y lecturas adicionales

- [curl_cffi documentation: Proxies](https://curl-cffi.readthedocs.io/en/latest/advanced.html#proxies)
- [curl_cffi API reference: Session (proxy, proxies, proxy_auth, trust_env, retry)](https://curl-cffi.readthedocs.io/en/latest/api.html#curl_cffi.requests.Session)
- [curl_cffi API reference: AsyncSession](https://curl-cffi.readthedocs.io/en/latest/api.html#curl_cffi.requests.AsyncSession)
- [curl_cffi API reference: Exceptions and warnings (ProxyError, ConnectionError)](https://curl-cffi.readthedocs.io/en/latest/api.html#exceptions-and-warnings)
- [curl_cffi documentation: Using CURLOPT_* in requests API (curl_options)](https://curl-cffi.readthedocs.io/en/latest/advanced.html#using-curlopt-in-requests-api)
- [curl_cffi issue #326: CONNECT was sent for http:// URLs, changed in 0.7.0b7](https://github.com/lexiforest/curl_cffi/issues/326)
- [libcurl: CURLOPT_PROXY (schemes, the empty string, socks5h)](https://curl.se/libcurl/c/CURLOPT_PROXY.html)
- [libcurl: CURLOPT_NOPROXY and the no_proxy variable](https://curl.se/libcurl/c/CURLOPT_NOPROXY.html)
- [libcurl environment variables: http_proxy is only used lowercase](https://curl.se/libcurl/c/libcurl-env.html)
- [Everything curl: proxy environment variables and the CGI reason](https://everything.curl.dev/usingcurl/proxies/env.html)
- [libcurl error codes: CURLE_COULDNT_CONNECT (7), CURLE_RECV_ERROR (56), CURLE_PROXY (97)](https://curl.se/libcurl/c/libcurl-errors.html)

## Guías relacionadas

- [Corrige el error de proxy 407 sin adivinar](https://ipvolt.com/es/guides/fix-proxy-error-407.md)
- [Usar un proxy con curl: -x, variables de entorno, SOCKS5 y auth](https://ipvolt.com/es/guides/curl-proxy-setup.md)
- [Variables de entorno de proxy: HTTP_PROXY y NO_PROXY](https://ipvolt.com/es/guides/proxy-environment-variables.md)

## Sobre ipvolt

Los ejemplos usan configuraciones de proxy genéricas, con enlaces a la documentación técnica original. El comportamiento específico de cada producto debe comprobarse con tu proveedor. ipvolt sigue en desarrollo.

[Leer el original en inglés](https://ipvolt.com/guides/curl-cffi-proxy-setup.md)

## Entérate cuando se abra el acceso.

ipvolt · En desarrollo

Estamos construyendo infraestructura de proxies para desarrolladores y equipos de datos. Únete a la lista de interés para recibir un aviso cuando ipvolt esté listo.

Un correo cuando se abra el acceso. Nada más.

[Solicitar acceso anticipado](https://ipvolt.com/es/guides/curl-cffi-proxy-setup#waitlist-closing)

[Privacidad](https://ipvolt.com/privacy)

