← Retour au blog

Variables HTTP_PROXY, HTTPS_PROXY, NO_PROXY sous Linux, Docker et CI : 7 erreurs courantes

Pourquoi les variables d'environnement proxy fonctionnent dans un conteneur et échouent dans un autre - nous examinons 7 cas réels avec des solutions et du code.

📅7 novembre 2026

Les variables d'environnement HTTP_PROXY, HTTPS_PROXY et NO_PROXY semblent extrêmement simples : vous définissez la variable — tout le trafic passe par le proxy. En pratique, c'est précisément à ce stade que les développeurs perdent des heures à déboguer : le proxy fonctionne dans le terminal, mais ne fonctionne pas dans le conteneur Docker ; curl passe par le proxy, mais l'application Python ne le fait pas ; le CI-runner bloque soudainement le registre interne, car NO_PROXY est spécifié avec une syntaxe incorrecte. Nous allons examiner 7 pièges réels et montrer comment les contourner avec du code concret.

Comment les applications lisent les variables proxy

Il est important de comprendre : HTTP_PROXY, HTTPS_PROXY et NO_PROXY ne sont pas un standard du protocole HTTP, mais une convention que chaque bibliothèque implémente à sa manière. Le noyau Linux ne sait rien de ces variables — elles sont lues par une application ou une bibliothèque spécifique lors de l'initialisation du client réseau.

Par exemple, curl et les utilitaires basés sur libcurl lisent les variables à chaque lancement de processus. La bibliothèque Python requests les lit via le module urllib.request.getproxies(), qui scanne l'environnement du processus au moment de la création de la session. En revanche, la fonction intégrée fetch() dans Node.js jusqu'à la version 18 ne prend pas en compte ces variables sans bibliothèques supplémentaires.

D'où la première règle : la variable proxy fonctionne uniquement là où elle est explicitement supportée par la bibliothèque spécifique. Vérifier via curl ne garantit pas que votre application en Go, Java ou Node.js se comportera de la même manière.

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 affichera dans le log verbose : "Trying proxy.example.com..."

Piège 1 : la casse des variables et vulnérabilité httpoxy

La plupart des utilitaires de console sous Linux lisent les variables en minuscules : http_proxy, https_proxy, no_proxy. De nombreux SDK modernes se basent sur la casse supérieure. La différence semble minime, mais c'est précisément à cause de cela que la moitié des bugs de type "fonctionne localement, ne fonctionne pas sur le serveur" se produisent dès le premier jour de configuration.

Une histoire à part est la vulnérabilité httpoxy (CVE-2016-5385 et connexes). Le principe est que l'environnement CGI (PHP, Python WSGI, certains frameworks Ruby) transforme automatiquement l'en-tête HTTP Proxy de la requête entrante en variable d'environnement HTTP_PROXY. Un attaquant envoie l'en-tête Proxy: http://attacker.com, et votre application serveur commence à envoyer des requêtes sortantes via un serveur proxy tiers, ce qui ouvre la voie à des attaques MITM et à des fuites de données.

La solution consiste à toujours définir les variables dans les deux casses et à explicitement annuler HTTP_PROXY au niveau du serveur web pour les requêtes entrantes :

# Définir dans les deux casses pour la compatibilité
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"

# Dans nginx — bloquer l'en-tête Proxy du client
proxy_set_header Proxy "";

Piège 2 : la syntaxe NO_PROXY varie entre les outils

NO_PROXY est une liste d'exceptions, mais le format d'enregistrement des domaines et des masques n'est pas standardisé. Certains outils comprennent .example.com comme "tout sous-domaine", d'autres exigent *.example.com, d'autres comprennent uniquement la correspondance exacte de l'hôte sans wildcard.

Par exemple, la bibliothèque Go net/http prend en charge la notation CIDR dans NO_PROXY (10.0.0.0/8), tandis que le curl classique jusqu'à certaines versions ne le fait pas. Cela conduit à une situation où le même NO_PROXY exclut correctement le registre interne pour une étape CI et ne l'exclut pas pour une autre, écrite dans un autre langage.

Outil Wildcard sous-domaine Masque CIDR
curl / libcurl via .example.com non
Go net/http via .example.com oui
Python requests via .example.com non
npm / Node.js besoin de *.example.com non

Conseil pratique : énumérez à la fois la version de domaine et la version wildcard dans NO_PROXY pour couvrir différents parseurs :

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

Piège 3 : Docker build vs Docker run — différentes variables d'environnement

C'est probablement le piège le plus fréquent dans Docker. Les variables définies via docker run -e HTTP_PROXY=... ne sont actives que pendant l'exécution du conteneur. Elles NE sont PAS automatiquement transférées à l'étape de construction de l'image. Si votre Dockerfile appelle apt-get install ou pip install, et que la construction se fait derrière un proxy d'entreprise, la construction va simplement se bloquer sur un timeout.

Pour la construction, vous devez passer des build-args, que Docker comprend "par défaut" depuis la version 1.9 sans déclaration dans le 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 vous souhaitez que la variable reste accessible à l'intérieur du conteneur en cours d'exécution, vous devez explicitement la dupliquer via ENV dans le Dockerfile — ARG et ENV dans Docker sont fondamentalement des mécanismes différents, et ARG disparaît immédiatement après la construction de la couche :

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

Un autre détail : le démon Docker peut lui-même utiliser un proxy pour télécharger des images depuis le registre — cela se configure non pas dans le Dockerfile, mais dans la configuration de l'unité systemd /etc/systemd/system/docker.service.d/http-proxy.conf, ce qui est souvent confondu avec les variables à l'intérieur du conteneur.

Piège 4 : docker-compose et héritage des variables entre services

Dans docker-compose, les variables d'environnement de l'hôte NE sont PAS automatiquement transmises aux conteneurs, sauf si vous les avez explicitement définies dans la section environment ou si vous utilisez un fichier .env. Une erreur fréquente consiste à définir le proxy dans le terminal avant de lancer docker-compose up et de s'attendre à ce que tous les services l'attrapent.

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

Notez que NO_PROXY : à l'intérieur des réseaux docker-compose, les conteneurs s'adressent les uns aux autres par les noms de services (db, redis), et non par localhost. Si vous oubliez d'ajouter les noms de services dans les exceptions, tout le trafic interne entre les conteneurs passera également par le proxy externe, ce qui augmentera les latences et pourrait casser la connexion si le proxy ne laisse pas passer des protocoles internes comme PostgreSQL ou Redis.

Piège 5 : CI/CD runners et fuite de secrets proxy

Dans GitHub Actions, GitLab CI et Jenkins, les variables proxy sont souvent définies au niveau du runner ou de l'organisation entière, ce qui crée deux problèmes. Premièrement, si l'URL du proxy contient un login et un mot de passe en clair, il apparaît dans le log de construction pour toute commande qui imprime l'environnement (env, printenv, sortie de débogage dans les scripts). Deuxièmement, les runners auto-hébergés héritent parfois du proxy système de l'hôte, qui n'est pas destiné au trafic des jobs 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: Test de connexion
        run: curl -sS https://api.example.com/health

Règle de sécurité : stockez l'URL du proxy uniquement dans les secrets CI (GitHub Secrets, GitLab CI/CD Variables avec le drapeau "masked"), ne jamais imprimer les variables d'environnement en entier dans les logs de débogage, et limiter NO_PROXY de sorte que les appels à la plateforme CI elle-même (par exemple, github.com, gitlab.com) ne passent pas par le proxy — sinon, la construction peut se bloquer si le proxy n'est pas disponible au moment de la charge maximale.

Piège 6 : caractères spéciaux dans le login et le mot de passe du proxy

Si le mot de passe du proxy contient des caractères @, :, / ou #, le parseur d'URL casse simplement la chaîne en morceaux aux mauvais endroits. L'erreur classique : le mot de passe P@ssw0rd dans la chaîne http://user:P@[email protected]:8080 est interprété par le parseur comme si l'hôte était "[email protected]", et non "proxy.com".

La solution consiste à toujours encoder l'URL du login et du mot de passe avant de les substituer dans la variable :

# Python : encoder le mot de passe avant de construire la chaîne 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 : construction similaire via 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;

Piège 7 : applications qui ignorent les variables d'environnement

Tous les clients HTTP ne respectent pas les variables proxy système. En Python, la bibliothèque requests les lit automatiquement via requests.Session(), mais si vous avez une fois passé explicitement un dictionnaire proxies={} dans l'appel, cela écrase les variables d'environnement et le trafic ira directement, même si HTTP_PROXY est défini dans le système.

import requests

# Dictionnaire proxies vide ÉCRASE les variables d'environnement
response = requests.get("https://api.example.com", proxies={})
# Correct — passer explicitement le proxy ou ne pas passer proxies du tout
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, la fonction intégrée fetch ne connaît pas du tout HTTP_PROXY sans bibliothèques supplémentaires. Vous devez explicitement connecter undici avec ProxyAgent ou le classique 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 nécessite également des propriétés système distinctes au lieu de variables d'environnement : -Dhttp.proxyHost=proxy.example.com -Dhttp.proxyPort=8080 -Dhttps.proxyHost=proxy.example.com -Dhttps.proxyPort=8080. Les variables d'environnement HTTP_PROXY dans la JVM ne sont pas lues par défaut.

Comment tester correctement les variables proxy

Avant de perdre du temps à déboguer l'application, vérifiez la variable elle-même et le proxy isolément du code. Cela vous fait économiser des heures.

# 1. Vérifiez que la variable est réellement définie dans la session actuelle
env | grep -i proxy

# 2. Vérifiez le fonctionnement du proxy directement via curl
curl -x "$HTTP_PROXY" -v https://api.ipify.org

# 3. Vérifiez à l'intérieur du conteneur que la variable est bien passée
docker exec -it my_container env | grep -i proxy

# 4. Vérifiez que NO_PROXY exclut réellement l'hôte souhaité
curl -v --noproxy "internal.corp" https://service.internal.corp/health

Si la tâche consiste à effectuer des requêtes massives vers des API externes, à parser des marketplaces ou à tester le géo-ciblage de la publicité depuis un pipeline CI, il est important de choisir le bon type de proxy pour la charge. Pour des scénarios stables à fort trafic dans CI, il suffit généralement d'utiliser des proxies de datacenter — ils sont plus rapides et moins chers que les résidents lors de la circulation massive des builds et des tests. Et si votre pipeline teste le comportement d'un site avec l'IP réelle d'un utilisateur d'une région spécifique, il est préférable de se connecter à des proxies résidentiels — ils réduisent le risque de blocage en raison de l'IP "datacenter".

Liste de contrôle avant le déploiement

  • Les variables sont définies à la fois en majuscules et en minuscules
  • Le login et le mot de passe dans l'URL du proxy sont encodés via urllib.parse.quote ou encodeURIComponent
  • NO_PROXY contient à la fois le format de domaine et le format wildcard pour la compatibilité entre les outils
  • Dans docker-compose, NO_PROXY inclut les noms des services du réseau Compose, et pas seulement localhost
  • Pour Docker build, les build-args sont passés séparément des env d'exécution via ENV dans le Dockerfile
  • Les secrets contenant l'URL du proxy dans CI sont masqués et ne sont pas imprimés dans les logs de débogage
  • Pour les applications Java, le proxy est défini via des propriétés système -D, et non seulement via env
  • La protection contre httpoxy a été vérifiée — l'en-tête Proxy du client est annulé au niveau du serveur web

Conclusion

Les variables HTTP_PROXY, HTTPS_PROXY et NO_PROXY sont une convention qui semble simple, mais qui se casse en pratique à cause de la casse des caractères, de la syntaxe des exceptions, des frontières entre les étapes Docker build et Docker run, de l'héritage de l'environnement dans docker-compose, des fuites de secrets dans les logs CI et, enfin, du fait que toutes les bibliothèques ne lisent pas ces variables. Vérifiez chaque variable isolément via curl et docker exec avant de chercher un bug dans la logique métier de l'application.

Si votre infrastructure est liée au parsing de sites externes, au test du géo-ciblage de la publicité ou à la contournement des blocages par IP depuis un pipeline CI, il est important non seulement de propager correctement les variables d'environnement, mais aussi de choisir le type de proxy approprié pour la tâche. Pour tester le trafic mobile et les applications qui vérifient le comportement avec des opérateurs mobiles, des proxies mobiles conviennent — ils fournissent des IP de réseaux cellulaires réels et sont moins souvent filtrés par les systèmes anti-fraude.