← Voltar ao blog

Variáveis HTTP_PROXY, HTTPS_PROXY, NO_PROXY no Linux, Docker e CI: 7 erros comuns

Por que as variáveis de ambiente proxy funcionam em um contêiner e falham em outro - analisamos 7 casos reais com soluções e código.

📅7 de novembro de 2026

As variáveis de ambiente HTTP_PROXY, HTTPS_PROXY e NO_PROXY parecem extremamente simples: defina a variável — todo o tráfego passa pelo proxy. Na prática, é exatamente nesse ponto que os desenvolvedores perdem horas depurando: o proxy funciona no terminal, mas não funciona no contêiner Docker; o curl passa pelo proxy, mas o aplicativo em Python não; o CI runner de repente bloqueia o registro interno porque o NO_PROXY está especificado com uma sintaxe errada. Vamos analisar 7 armadilhas reais e mostrar como contorná-las com código específico.

Como os aplicativos leem as variáveis de proxy

É importante entender: HTTP_PROXY, HTTPS_PROXY e NO_PROXY não são um padrão do protocolo HTTP, mas uma convenção que cada biblioteca implementa de sua própria maneira. O núcleo do Linux não sabe nada sobre essas variáveis — elas são lidas por um aplicativo ou biblioteca específica na fase de inicialização do cliente de rede.

Por exemplo, curl e utilitários baseados em libcurl leem as variáveis a cada execução do processo. A biblioteca Python requests as lê através do módulo urllib.request.getproxies(), que escaneia o ambiente do processo no momento da criação da sessão. Já o fetch() embutido no Node.js até a versão 18 não considera essas variáveis sem pacotes adicionais.

Daí vem a primeira regra: a variável de proxy funciona apenas onde é explicitamente suportada pela biblioteca específica. A verificação através do curl não garante que seu aplicativo em Go, Java ou Node.js se comportará da mesma forma.

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á no log verbose: "Trying proxy.example.com..."

Armadilha 1: registro de variáveis e vulnerabilidade httpoxy

A maioria das utilitários de console no Linux lê as variáveis em letras minúsculas: http_proxy, https_proxy, no_proxy. Muitas SDKs modernas se orientam para letras maiúsculas. A diferença pode parecer pequena, mas é exatamente por causa dela que metade dos bugs do tipo "funciona localmente, não funciona no servidor" acontece no primeiro dia de configuração.

Uma história separada é a vulnerabilidade httpoxy (CVE-2016-5385 e relacionadas). A essência é que o ambiente CGI (PHP, Python WSGI, alguns frameworks Ruby) automaticamente transforma o cabeçalho HTTP Proxy da requisição de entrada em uma variável de ambiente HTTP_PROXY. Um invasor envia o cabeçalho Proxy: http://attacker.com, e seu aplicativo de servidor começa a enviar requisições de saída através de um proxy externo, abrindo caminho para ataques MITM e vazamento de dados.

A solução é sempre definir as variáveis em ambos os registros ao mesmo tempo e explicitamente zerar HTTP_PROXY no nível do servidor web para requisições de entrada:

# Definindo em ambos os registros para compatibilidade
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"

# No nginx — bloqueando o cabeçalho Proxy do cliente
proxy_set_header Proxy "";

Armadilha 2: a sintaxe NO_PROXY varia entre ferramentas

NO_PROXY é uma lista de exceções, mas o formato de registro de domínios e máscaras não é padronizado. Algumas ferramentas entendem .example.com como "qualquer subdomínio", outras exigem *.example.com, e outras entendem apenas a correspondência exata do host sem wildcard.

Por exemplo, a biblioteca Go net/http suporta notação CIDR em NO_PROXY (10.0.0.0/8), enquanto o curl clássico até certas versões não suporta. Isso leva a uma situação em que o mesmo NO_PROXY exclui corretamente o registro interno para um passo do CI e não exclui para outro, escrito em outra linguagem.

Ferramenta Wildcard de subdomínio Máscara CIDR
curl / libcurl através de .example.com não
Go net/http através de .example.com sim
Python requests através de .example.com não
npm / Node.js precisa de *.example.com não

Dica prática: liste no NO_PROXY tanto a versão de domínio quanto a versão wildcard ao mesmo tempo, para cobrir diferentes analisadores:

export NO_PROXY="localhost,127.0.0.1,.internal.corp,*.internal.corp,10.0.0.0/8"

Armadilha 3: Docker build vs Docker run — diferentes variáveis de ambiente

Esta é, sem dúvida, a armadilha mais comum no Docker. As variáveis definidas através de docker run -e HTTP_PROXY=... só são válidas durante a execução do contêiner. Elas NÃO são automaticamente transferidas para a fase de construção da imagem. Se seu Dockerfile chama apt-get install ou pip install, e a construção está passando pelo proxy corporativo, a construção simplesmente travará no timeout.

Para a construção, você precisa passar build-args, que o Docker entende "out of the box" a partir da versão 1.9 sem declaração no 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 .

Se você quiser que a variável permaneça acessível dentro do contêiner em execução, precisa duplicá-la explicitamente através de ENV no Dockerfile — ARG e ENV no Docker são mecanismos fundamentalmente diferentes, e ARG desaparece imediatamente após a construção da camada:

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

Outro detalhe: o daemon do Docker pode usar um proxy para baixar imagens do registro — isso é configurado não no Dockerfile, mas na configuração da unidade systemd /etc/systemd/system/docker.service.d/http-proxy.conf, o que muitas vezes é confundido com variáveis dentro do contêiner.

Armadilha 4: docker-compose e herança de variáveis entre serviços

No docker-compose, as variáveis de ambiente do host NÃO são passadas automaticamente para os contêineres, a menos que você as tenha explicitamente definido na seção environment ou esteja usando um arquivo .env. Um erro comum é definir o proxy no terminal antes de executar docker-compose up e esperar que todos os serviços o reconheçam.

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

Note que NO_PROXY: dentro da rede docker-compose, os contêineres se comunicam entre si pelos nomes dos serviços (db, redis), e não por localhost. Se você esquecer de adicionar os nomes dos serviços nas exceções, todo o tráfego interno entre os contêineres também passará pelo proxy externo, o que aumentará a latência e pode quebrar a conexão se o proxy não permitir protocolos internos como PostgreSQL ou Redis.

Armadilha 5: runners CI/CD e vazamento de segredos do proxy

No GitHub Actions, GitLab CI e Jenkins, as variáveis de proxy são frequentemente definidas no nível do runner ou da organização como um todo, e isso cria dois problemas. Primeiro, se a URL do proxy contém login e senha em texto claro, ela aparece no log de construção em qualquer comando que imprime o ambiente (env, printenv, saída de depuração em scripts). Em segundo lugar, runners auto-hospedados às vezes herdam o proxy do sistema host, que não é destinado ao tráfego da tarefa 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: Testar conexão
        run: curl -sS https://api.example.com/health

Regra de segurança: armazene a URL do proxy apenas em segredos CI (GitHub Secrets, GitLab CI/CD Variables com a flag "masked"), nunca imprima as variáveis de ambiente inteiras nos logs de depuração, e limite o NO_PROXY de forma que as requisições à própria plataforma CI (por exemplo, github.com, gitlab.com) não passem pelo proxy — caso contrário, a construção pode travar se o proxy não estiver disponível durante o pico de carga.

Armadilha 6: caracteres especiais no login e senha do proxy

Se a senha do proxy contém caracteres como @, :, / ou #, o analisador de URL simplesmente quebra a string nos lugares errados. Um erro clássico: a senha P@ssw0rd na string http://user:P@[email protected]:8080 é lida pelo analisador como se o host fosse "[email protected]", e não "proxy.com".

A solução é sempre codificar a URL do login e senha antes de inseri-las na variável:

# Python: codificando a senha antes de construir a string do 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: construção semelhante atravé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;

Armadilha 7: aplicativos que ignoram variáveis de ambiente

Nem todos os clientes HTTP respeitam as variáveis de proxy do sistema. Na biblioteca Python requests, elas são lidas automaticamente através de requests.Session(), mas se você uma vez passou explicitamente um dicionário proxies={} na chamada, isso sobrescreve as variáveis de ambiente e o tráfego irá diretamente, mesmo que HTTP_PROXY esteja definido no sistema.

import requests

# Dicionário proxies vazio SOBRESCREVE as variáveis de ambiente
response = requests.get("https://api.example.com", proxies={})
# Correto — passar explicitamente o proxy ou não passar proxies de forma alguma
proxies = {
    "http": "http://user:[email protected]:8080",
    "https": "http://user:[email protected]:8080",
}
response = requests.get("https://api.example.com", proxies=proxies)

No Node.js, o fetch embutido não sabe nada sobre HTTP_PROXY sem bibliotecas adicionais. É necessário conectar explicitamente undici com ProxyAgent ou o clássico 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 também exige propriedades de sistema separadas em vez de variáveis de ambiente: -Dhttp.proxyHost=proxy.example.com -Dhttp.proxyPort=8080 -Dhttps.proxyHost=proxy.example.com -Dhttps.proxyPort=8080. As variáveis de ambiente HTTP_PROXY na JVM não são lidas por padrão.

Como testar corretamente as variáveis de proxy

Antes de gastar tempo depurando o aplicativo, verifique a variável em si e o proxy isoladamente do código. Isso economiza horas.

# 1. Verifique se a variável está realmente definida na sessão atual
env | grep -i proxy

# 2. Verifique a funcionalidade do proxy diretamente através do curl
curl -x "$HTTP_PROXY" -v https://api.ipify.org

# 3. Verifique de dentro do contêiner se a variável chegou
docker exec -it my_container env | grep -i proxy

# 4. Verifique se NO_PROXY realmente exclui o host necessário
curl -v --noproxy "internal.corp" https://service.internal.corp/health

Se a tarefa é fazer requisições em massa para APIs externas, parsing de marketplaces ou testar geotargeting de anúncios a partir de um pipeline CI, é importante escolher o tipo certo de proxy para a carga. Para cenários de alta carga estáveis em CI, geralmente é suficiente usar proxies de data center — eles são mais rápidos e mais baratos que os residenciais para tráfego em massa de builds e testes. E se seu pipeline verifica o comportamento do site a partir do IP de um usuário real de uma região específica, é melhor usar proxies residenciais — eles reduzem o risco de bloqueio por serem identificados como IPs de "data center".

Checklist antes do deploy

  • As variáveis estão definidas tanto em maiúsculas quanto em minúsculas ao mesmo tempo
  • O login e a senha na URL do proxy estão codificados em URL através de urllib.parse.quote ou encodeURIComponent
  • NO_PROXY contém tanto o formato de domínio quanto o formato wildcard para compatibilidade entre ferramentas
  • No docker-compose, NO_PROXY inclui os nomes dos serviços da rede Compose, e não apenas localhost
  • Para Docker build, os build-args foram passados separadamente do env de runtime através de ENV no Dockerfile
  • Segredos com a URL do proxy no CI estão mascarados e não são impressos nos logs de depuração
  • Para aplicativos Java, o proxy é definido através de propriedades de sistema -D, e não apenas através de env
  • Verificada a proteção contra httpoxy — o cabeçalho Proxy do cliente é zerado no nível do servidor web

Conclusão

As variáveis HTTP_PROXY, HTTPS_PROXY e NO_PROXY são uma convenção que parece simples, mas que na prática quebra-se devido ao registro de caracteres, sintaxe de exceções, limites entre as fases de Docker build e Docker run, herança de ambiente no docker-compose, vazamentos de segredos nos logs do CI e, finalmente, o fato de que nem toda biblioteca lê essas variáveis. Verifique cada variável isoladamente através de curl e docker exec antes de procurar bugs na lógica de negócios do aplicativo.

Se sua infraestrutura depende de parsing de sites externos, teste de geotargeting de anúncios ou contorno de bloqueios por IP a partir de um pipeline CI, é importante não apenas passar as variáveis de ambiente corretamente, mas também escolher o tipo de proxy adequado para a tarefa. Para testar tráfego móvel e aplicativos que verificam o comportamento a partir de operadoras móveis, proxies móveis são adequados — eles fornecem IPs de redes móveis reais e raramente são filtrados por sistemas antifraude.