Las variables de entorno HTTP_PROXY, HTTPS_PROXY y NO_PROXY parecen extremadamente simples: se establece una variable y todo el tráfico pasa a través del proxy. En la práctica, es en este punto donde los desarrolladores pierden horas depurando: el proxy funciona en la terminal, pero no en el contenedor de Docker; curl pasa a través del proxy, pero la aplicación en Python no; el runner de CI de repente bloquea el registro interno porque NO_PROXY está especificado con una sintaxis incorrecta. Analizaremos 7 trampas reales y mostraremos cómo evitarlas con código específico.
Cómo las aplicaciones leen las variables proxy
Es importante entender: HTTP_PROXY, HTTPS_PROXY y NO_PROXY no son un estándar del protocolo HTTP, sino una convención que cada biblioteca implementa a su manera. El núcleo de Linux no sabe nada sobre estas variables; son leídas por una aplicación o biblioteca específica en el momento de inicializar el cliente de red.
Por ejemplo, curl y las utilidades basadas en libcurl leen las variables en cada ejecución del proceso. La biblioteca de Python requests las lee a través del módulo urllib.request.getproxies(), que escanea el entorno del proceso en el momento de crear la sesión. Sin embargo, el fetch() integrado en Node.js hasta la versión 18 no tiene en cuenta estas variables sin paquetes adicionales.
De aquí surge la primera regla: la variable proxy solo funciona donde es explícitamente soportada por una biblioteca específica. Verificar a través de curl no garantiza que tu aplicación en Go, Java o Node.js se comporte de la misma manera.
export HTTP_PROXY="http://user:[email protected]:8080"
export HTTPS_PROXY="http://user:[email protected]:8080"
export NO_PROXY="localhost,127.0.0.1,.internal.corp"
curl -v https://api.example.com/ping
# curl mostrará en el registro verbose: "Trying proxy.example.com..."
Trampa 1: el registro de variables y la vulnerabilidad httpoxy
La mayoría de las utilidades de consola en Linux leen las variables en minúsculas: http_proxy, https_proxy, no_proxy. Muchos SDK modernos se basan en mayúsculas. La diferencia parece pequeña, pero es precisamente por esto que la mitad de los errores del tipo "funciona localmente, no funciona en el servidor" ocurren en el primer día de configuración.
Una historia aparte es la vulnerabilidad httpoxy (CVE-2016-5385 y relacionadas). La esencia es que el entorno CGI (PHP, Python WSGI, algunos frameworks de Ruby) convierte automáticamente el encabezado HTTP Proxy de la solicitud entrante en la variable de entorno HTTP_PROXY. Un atacante envía el encabezado Proxy: http://attacker.com, y tu aplicación del servidor comienza a enviar solicitudes salientes a través de un servidor proxy ajeno, lo que abre la puerta a un ataque MITM y a la filtración de datos.
La solución es siempre establecer las variables en ambos registros a la vez y anular explícitamente HTTP_PROXY a nivel del servidor web para las solicitudes entrantes:
# Establecemos en ambos registros para compatibilidad
export http_proxy="http://proxy.example.com:8080"
export HTTP_PROXY="http://proxy.example.com:8080"
export https_proxy="http://proxy.example.com:8080"
export HTTPS_PROXY="http://proxy.example.com:8080"
# En nginx — bloqueamos el encabezado Proxy del cliente
proxy_set_header Proxy "";
Trampa 2: la sintaxis de NO_PROXY varía entre herramientas
NO_PROXY es una lista de excepciones, pero el formato para registrar dominios y máscaras no está estandarizado. Algunas herramientas entienden .example.com como "cualquier subdominio", otras requieren *.example.com, y otras solo entienden coincidencias exactas de host sin wildcard.
Por ejemplo, la biblioteca Go net/http admite notación CIDR en NO_PROXY (10.0.0.0/8), mientras que curl clásico hasta ciertas versiones no lo hace. Esto lleva a situaciones en las que el mismo NO_PROXY excluye correctamente el registro interno para un paso de CI y no lo excluye para otro escrito en otro lenguaje.
| Herramienta | Wildcard de subdominio | Máscara CIDR |
|---|---|---|
| curl / libcurl | a través de .example.com | no |
| Go net/http | a través de .example.com | sí |
| Python requests | a través de .example.com | no |
| npm / Node.js | necesita *.example.com | no |
Consejo práctico: enumera en NO_PROXY tanto la variante de dominio como la variante wildcard al mismo tiempo, para cubrir diferentes analizadores:
export NO_PROXY="localhost,127.0.0.1,.internal.corp,*.internal.corp,10.0.0.0/8"
Trampa 3: Docker build vs Docker run — diferentes variables de entorno
Esta es, quizás, la trampa más común en Docker. Las variables establecidas a través de docker run -e HTTP_PROXY=... solo son efectivas durante la ejecución del contenedor. NO se transfieren automáticamente a la etapa de construcción de la imagen. Si tu Dockerfile llama a apt-get install o pip install, y la construcción se realiza detrás de un proxy corporativo, la construcción simplemente se quedará colgada en un tiempo de espera.
Para la construcción, es necesario pasar build-args, que Docker entiende "out of the box" desde la versión 1.9+ sin necesidad de declararlos en el Dockerfile:
docker build \
--build-arg HTTP_PROXY=http://proxy.example.com:8080 \
--build-arg HTTPS_PROXY=http://proxy.example.com:8080 \
--build-arg NO_PROXY=localhost,127.0.0.1 \
-t my-app:latest .
Si deseas que la variable permanezca disponible dentro del contenedor en ejecución, debes duplicarla explícitamente a través de ENV en el Dockerfile; ARG y ENV en Docker son mecanismos fundamentalmente diferentes, y ARG desaparece inmediatamente después de la construcción de la capa:
ARG HTTP_PROXY
ARG HTTPS_PROXY
ARG NO_PROXY
ENV HTTP_PROXY=${HTTP_PROXY}
ENV HTTPS_PROXY=${HTTPS_PROXY}
ENV NO_PROXY=${NO_PROXY}
RUN apt-get update && apt-get install -y curl
Otro matiz: el daemon de Docker puede usar un proxy para descargar imágenes del registro; esto se configura no en el Dockerfile, sino en la configuración del unit de systemd /etc/systemd/system/docker.service.d/http-proxy.conf, lo que a menudo se confunde con las variables dentro del contenedor.
Trampa 4: docker-compose y la herencia de variables entre servicios
En docker-compose, las variables de entorno del host NO se pasan automáticamente a los contenedores, a menos que las hayas especificado explícitamente en la sección environment o uses un archivo .env. Un error común es establecer el proxy en la terminal antes de ejecutar docker-compose up y esperar que todos los servicios lo recojan.
version: "3.9"
services:
scraper:
build:
context: .
args:
HTTP_PROXY: "${HTTP_PROXY}"
HTTPS_PROXY: "${HTTPS_PROXY}"
environment:
- HTTP_PROXY=${HTTP_PROXY}
- HTTPS_PROXY=${HTTPS_PROXY}
- NO_PROXY=localhost,127.0.0.1,db,redis
depends_on:
- db
- redis
Presta atención a NO_PROXY: dentro de la red de docker-compose, los contenedores se comunican entre sí por los nombres de los servicios (db, redis), no por localhost. Si olvidas agregar los nombres de los servicios a las excepciones, todo el tráfico interno entre contenedores también pasará a través del proxy externo, lo que aumentará la latencia y puede romper la conexión si el proxy no permite protocolos internos como PostgreSQL o Redis.
Trampa 5: runners de CI/CD y filtraciones de secretos proxy
En GitHub Actions, GitLab CI y Jenkins, las variables proxy a menudo se establecen a nivel del runner o de la organización en su totalidad, lo que crea dos problemas. Primero, si la URL del proxy contiene un nombre de usuario y una contraseña en texto claro, aparece en el registro de construcción con cualquier comando que imprima el entorno (env, printenv, salida de depuración en scripts). En segundo lugar, los runners autoalojados a veces heredan el proxy del sistema del host, que no está destinado al tráfico de la tarea de CI.
# .github/workflows/build.yml
jobs:
build:
runs-on: ubuntu-latest
env:
HTTP_PROXY: ${{ secrets.PROXY_URL }}
HTTPS_PROXY: ${{ secrets.PROXY_URL }}
NO_PROXY: "localhost,127.0.0.1,github.com"
steps:
- uses: actions/checkout@v4
- name: Probar conexión
run: curl -sS https://api.example.com/health
Regla de seguridad: almacena la URL del proxy solo en secretos de CI (GitHub Secrets, GitLab CI/CD Variables con la bandera "masked"), nunca imprimas las variables de entorno en los registros de depuración, y limita NO_PROXY para que las solicitudes a la propia plataforma de CI (por ejemplo, github.com, gitlab.com) no pasen a través del proxy; de lo contrario, la construcción puede colgarse si el proxy no está disponible en momentos de alta carga.
Trampa 6: caracteres especiales en el nombre de usuario y la contraseña del proxy
Si la contraseña del proxy contiene caracteres @, :, / o #, el analizador de URL simplemente rompe la cadena en partes en lugares incorrectos. Un error clásico: la contraseña P@ssw0rd en la cadena http://user:P@[email protected]:8080 es leída por el analizador como si el host fuera "[email protected]", y no "proxy.com".
La solución es siempre codificar en URL el nombre de usuario y la contraseña antes de insertarlos en la variable:
# Python: codificamos la contraseña antes de construir la cadena del proxy
import urllib.parse
user = "login"
password = "P@ss:w0rd/1"
encoded_password = urllib.parse.quote(password, safe="")
proxy_url = f"http://{user}:{encoded_password}@proxy.example.com:8080"
import os
os.environ["HTTPS_PROXY"] = proxy_url
# Node.js: construcción similar a través de encodeURIComponent
const user = "login";
const password = "P@ss:w0rd/1";
const encoded = encodeURIComponent(password);
const proxyUrl = `http://${user}:${encoded}@proxy.example.com:8080`;
process.env.HTTPS_PROXY = proxyUrl;
Trampa 7: aplicaciones que ignoran las variables de entorno
No todos los clientes HTTP respetan las variables proxy del sistema. En Python, la biblioteca requests las lee automáticamente a través de requests.Session(), pero si una vez has pasado explícitamente un diccionario proxies={} en la llamada, esto anula las variables de entorno y el tráfico irá directamente, incluso si HTTP_PROXY está establecido en el sistema.
import requests
# Un diccionario vacío de proxies ANULA las variables de entorno
response = requests.get("https://api.example.com", proxies={})
# Correcto — pasar explícitamente el proxy o no pasar proxies en absoluto
proxies = {
"http": "http://user:[email protected]:8080",
"https": "http://user:[email protected]:8080",
}
response = requests.get("https://api.example.com", proxies=proxies)
En Node.js, el fetch integrado no sabe nada de HTTP_PROXY sin bibliotecas adicionales. Debes conectar explícitamente undici con ProxyAgent o el clásico https-proxy-agent:
const { ProxyAgent, setGlobalDispatcher } = require("undici");
const proxyUrl = process.env.HTTPS_PROXY;
if (proxyUrl) {
setGlobalDispatcher(new ProxyAgent(proxyUrl));
}
fetch("https://api.example.com/data")
.then(res => res.json())
.then(console.log);
Java también requiere propiedades del sistema separadas en lugar de variables de entorno: -Dhttp.proxyHost=proxy.example.com -Dhttp.proxyPort=8080 -Dhttps.proxyHost=proxy.example.com -Dhttps.proxyPort=8080. Las variables de entorno HTTP_PROXY en la JVM no se leen por defecto.
Cómo probar correctamente las variables proxy
Antes de perder tiempo depurando la aplicación, verifica la variable misma y el proxy de manera aislada del código. Esto ahorra horas.
# 1. Verificamos que la variable esté realmente establecida en la sesión actual
env | grep -i proxy
# 2. Verificamos la funcionalidad del proxy directamente a través de curl
curl -x "$HTTP_PROXY" -v https://api.ipify.org
# 3. Verificamos desde dentro del contenedor que la variable ha llegado
docker exec -it my_container env | grep -i proxy
# 4. Verificamos que NO_PROXY realmente excluye el host deseado
curl -v --noproxy "internal.corp" https://service.internal.corp/health
Si la tarea es realizar solicitudes masivas a APIs externas, analizar marketplaces o probar la geo-targeting de publicidad desde un pipeline de CI, es importante elegir el tipo de proxy adecuado para la carga. Para escenarios estables de alta carga en CI, generalmente son suficientes proxies de centros de datos — son más rápidos y más baratos que los residenciales para el tráfico masivo de construcciones y pruebas. Y si tu pipeline verifica el comportamiento del sitio desde la IP de un usuario real de una región específica, es mejor conectar proxies residenciales — reducen el riesgo de bloqueo por la característica de IP "de centro de datos".
Lista de verificación antes del despliegue
- Las variables están establecidas tanto en mayúsculas como en minúsculas al mismo tiempo
- El nombre de usuario y la contraseña en la URL del proxy están codificados en URL a través de
urllib.parse.quoteoencodeURIComponent - NO_PROXY contiene tanto el formato de dominio como el formato wildcard para compatibilidad entre herramientas
- En docker-compose, NO_PROXY incluye los nombres de los servicios de la red Compose, y no solo localhost
- Para Docker build, se han pasado build-args separados del entorno de ejecución a través de ENV en el Dockerfile
- Los secretos con la URL del proxy en CI están enmascarados y no se imprimen en los registros de depuración
- Para aplicaciones Java, el proxy se establece a través de propiedades del sistema -D, y no solo a través de env
- Se ha verificado la protección contra httpoxy: el encabezado Proxy del cliente se anula a nivel del servidor web
Conclusión
Las variables HTTP_PROXY, HTTPS_PROXY y NO_PROXY son una convención que a simple vista parece simple, pero en la práctica se rompe por el registro de caracteres, la sintaxis de excepciones, los límites entre las etapas de Docker build y Docker run, la herencia de entorno en docker-compose, las filtraciones de secretos en los registros de CI y, finalmente, porque no todas las bibliotecas leen estas variables. Verifica cada variable de forma aislada a través de curl y docker exec antes de buscar errores en la lógica de negocio de la aplicación.
Si tu infraestructura depende del análisis de sitios externos, la prueba de geo-targeting de publicidad o eludir bloqueos por IP desde un pipeline de CI, es importante no solo pasar correctamente las variables de entorno, sino también elegir el tipo de proxy adecuado para la tarea. Para probar el tráfico móvil y aplicaciones que verifican el comportamiento desde operadores móviles, son adecuados proxies móviles — proporcionan IP de redes celulares reales y raramente son bloqueados por sistemas antifraude.