Le variabili d'ambiente HTTP_PROXY, HTTPS_PROXY e NO_PROXY sembrano estremamente semplici: imposti la variabile — tutto il traffico passa attraverso il proxy. Nella pratica, è proprio in questo punto che gli sviluppatori perdono ore nel debug: il proxy funziona nel terminale, ma non funziona nel contenitore Docker; curl passa attraverso il proxy, ma l'applicazione in Python no; il runner CI improvvisamente blocca il registry interno, perché NO_PROXY è specificato con una sintassi errata. Analizziamo 7 trappole reali e mostriamo come evitarle con codice specifico.
Come le applicazioni leggono le variabili proxy
È importante capire: HTTP_PROXY, HTTPS_PROXY e NO_PROXY non sono uno standard del protocollo HTTP, ma una convenzione che ogni libreria implementa a modo suo. Il kernel Linux non sa nulla di queste variabili — vengono lette da una specifica applicazione o libreria durante la fase di inizializzazione del client di rete.
Ad esempio, curl e le utility basate su libcurl leggono le variabili ad ogni avvio del processo. La libreria Python requests le legge tramite il modulo urllib.request.getproxies(), che scansiona l'ambiente del processo al momento della creazione della sessione. Tuttavia, il fetch() integrato in Node.js fino alla versione 18 non considera affatto queste variabili senza pacchetti aggiuntivi.
Da qui la prima regola: la variabile proxy funziona solo dove è esplicitamente supportata da una specifica libreria. Controllare tramite curl non garantisce che la vostra applicazione in Go, Java o Node.js si comporti allo stesso modo.
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 mostrerà nel log verbose: "Trying proxy.example.com..."
Trappola 1: case delle variabili e vulnerabilità httpoxy
La maggior parte delle utility da console in Linux legge le variabili in minuscolo: http_proxy, https_proxy, no_proxy. Molti SDK moderni si basano sul maiuscolo. La differenza sembra un dettaglio, ma è proprio per questo che metà dei bug del tipo "funziona in locale, non funziona sul server" si verifica nel primo giorno di configurazione.
Una storia a parte è la vulnerabilità httpoxy (CVE-2016-5385 e correlate). La questione è che l'ambiente CGI (PHP, Python WSGI, alcuni framework Ruby) trasforma automaticamente l'intestazione HTTP Proxy dalla richiesta in arrivo in una variabile d'ambiente HTTP_PROXY. Un attaccante invia l'intestazione Proxy: http://attacker.com, e la vostra applicazione server inizia a inviare richieste in uscita attraverso un proxy esterno, aprendo la strada a un attacco MITM e alla fuga di dati.
La soluzione è sempre impostare le variabili in entrambi i casi e azzerare esplicitamente HTTP_PROXY a livello di server web per le richieste in arrivo:
# Impostiamo in entrambi i casi per 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"
# In nginx — blocchiamo l'intestazione Proxy dal client
proxy_set_header Proxy "";
Trappola 2: la sintassi NO_PROXY varia tra gli strumenti
NO_PROXY è un elenco di eccezioni, ma il formato per la registrazione dei domini e delle maschere non è standardizzato. Alcuni strumenti interpretano .example.com come "qualsiasi sottodominio", altri richiedono *.example.com, altri ancora comprendono solo la corrispondenza esatta dell'host senza wildcard.
Ad esempio, la libreria Go net/http supporta la notazione CIDR in NO_PROXY (10.0.0.0/8), mentre il classico curl fino a determinate versioni — no. Questo porta a situazioni in cui lo stesso NO_PROXY esclude correttamente il registry interno per un passo CI e non lo esclude per un altro scritto in un altro linguaggio.
| Strumento | Wildcard sottodominio | Maschera CIDR |
|---|---|---|
| curl / libcurl | tramite .example.com | no |
| Go net/http | tramite .example.com | sì |
| Python requests | tramite .example.com | no |
| npm / Node.js | richiede *.example.com | no |
Consiglio pratico: elencate in NO_PROXY sia la variante di dominio che la variante wildcard contemporaneamente, per coprire diversi parser:
export NO_PROXY="localhost,127.0.0.1,.internal.corp,*.internal.corp,10.0.0.0/8"
Trappola 3: Docker build vs Docker run — diverse variabili d'ambiente
Questa è probabilmente la trappola più comune in Docker. Le variabili impostate tramite docker run -e HTTP_PROXY=... agiscono solo durante l'esecuzione del contenitore. Non vengono automaticamente trasferite alla fase di costruzione dell'immagine. Se il vostro Dockerfile chiama apt-get install o pip install, e la costruzione avviene dietro un proxy aziendale, la build semplicemente si bloccherà per timeout.
Per la costruzione è necessario passare build-args, che Docker dalla versione 1.9+ comprende "out of the box" senza dichiarazione nel 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 desiderate che la variabile rimanga accessibile anche all'interno del contenitore in esecuzione, è necessario duplicarla esplicitamente tramite ENV nel Dockerfile — ARG e ENV in Docker sono meccanismi fondamentalmente diversi, e ARG scompare subito dopo la costruzione del layer:
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 altro dettaglio: il demone Docker può utilizzare un proxy per scaricare immagini dal registry — questo si configura non nel Dockerfile, ma nella configurazione dell'unità systemd /etc/systemd/system/docker.service.d/http-proxy.conf, cosa che spesso viene confusa con le variabili all'interno del contenitore.
Trappola 4: docker-compose e ereditarietà delle variabili tra i servizi
In docker-compose, le variabili d'ambiente dell'host NON vengono propagate automaticamente nei contenitori, a meno che non siano state esplicitamente scritte nella sezione environment o non si utilizzi un file .env. Un errore comune è impostare il proxy nel terminale prima di eseguire docker-compose up e aspettarsi che tutti i servizi lo rilevino.
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
Fate attenzione a NO_PROXY: all'interno della rete docker-compose, i contenitori si riferiscono l'uno all'altro tramite i nomi dei servizi (db, redis), e non tramite localhost. Se dimenticate di aggiungere i nomi dei servizi nelle eccezioni, tutto il traffico interno tra i contenitori passerà anche attraverso il proxy esterno, aumentando la latenza e potenzialmente rompendo la connessione, se il proxy non consente protocolli interni come PostgreSQL o Redis.
Trappola 5: runner CI/CD e fuga di segreti del proxy
In GitHub Actions, GitLab CI e Jenkins, le variabili proxy sono spesso impostate a livello di runner o di organizzazione intera, e questo crea due problemi. In primo luogo, se l'URL del proxy contiene login e password in chiaro, esso finisce nel log di costruzione con qualsiasi comando che stampa l'ambiente (env, printenv, output di debug negli script). In secondo luogo, i runner self-hosted a volte ereditano il proxy di sistema dell'host, che non è destinato al traffico delle CI job.
# .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 connessione
run: curl -sS https://api.example.com/health
Regola di sicurezza: memorizzate l'URL del proxy solo nei segreti CI (GitHub Secrets, GitLab CI/CD Variables con flag "masked"), non stampate mai le variabili d'ambiente interamente nei log di debug, e limitate NO_PROXY in modo che le richieste alla piattaforma CI stessa (ad esempio, github.com, gitlab.com) non passino attraverso il proxy — altrimenti la costruzione potrebbe bloccarsi se il proxy non è disponibile durante i picchi di carico.
Trappola 6: caratteri speciali nel login e nella password del proxy
Se la password del proxy contiene caratteri @, :, / o #, il parser dell'URL semplicemente rompe la stringa in parti nei posti sbagliati. Un errore classico: la password P@ssw0rd nella stringa http://user:P@[email protected]:8080 viene letta dal parser come se l'host fosse "[email protected]", e non "proxy.com".
La soluzione è sempre URL-encode il login e la password prima di inserirli nella variabile:
# Python: codifichiamo la password prima di costruire la stringa 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: costruzione analoga tramite 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;
Trappola 7: applicazioni che ignorano le variabili d'ambiente
Non tutti i client HTTP rispettano le variabili proxy di sistema. In Python, la libreria requests le legge automaticamente tramite requests.Session(), ma se una volta avete passato esplicitamente un dizionario proxies={} nella chiamata, questo sovrascrive le variabili d'ambiente e il traffico andrà direttamente, anche se HTTP_PROXY è impostato nel sistema.
import requests
# Un dizionario proxies vuoto SOVRASCRIVE le variabili d'ambiente
response = requests.get("https://api.example.com", proxies={})
# Corretto — passare esplicitamente il proxy o non passare affatto proxies
proxies = {
"http": "http://user:[email protected]:8080",
"https": "http://user:[email protected]:8080",
}
response = requests.get("https://api.example.com", proxies=proxies)
In Node.js, il fetch integrato non conosce affatto HTTP_PROXY senza librerie aggiuntive. È necessario collegare esplicitamente undici con ProxyAgent o il classico 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);
Anche Java richiede proprietà di sistema separate invece delle variabili d'ambiente: -Dhttp.proxyHost=proxy.example.com -Dhttp.proxyPort=8080 -Dhttps.proxyHost=proxy.example.com -Dhttps.proxyPort=8080. Le variabili d'ambiente HTTP_PROXY nella JVM per impostazione predefinita non vengono lette affatto.
Come testare correttamente le variabili proxy
Prima di perdere tempo nel debug dell'applicazione, controllate la variabile stessa e il proxy isolatamente dal codice. Questo fa risparmiare ore.
# 1. Controlliamo che la variabile sia realmente impostata nella sessione corrente
env | grep -i proxy
# 2. Controlliamo il funzionamento del proxy direttamente tramite curl
curl -x "$HTTP_PROXY" -v https://api.ipify.org
# 3. Controlliamo dall'interno del contenitore che la variabile sia arrivata
docker exec -it my_container env | grep -i proxy
# 4. Controlliamo che NO_PROXY escluda realmente l'host desiderato
curl -v --noproxy "internal.corp" https://service.internal.corp/health
Se l'obiettivo è effettuare richieste massicce a API esterne, il parsing di marketplace o il test del geo-targeting della pubblicità da un pipeline CI, è importante scegliere il tipo di proxy giusto per il carico. Per scenari stabili e ad alta intensità in CI, di solito è sufficiente proxy di data center — sono più veloci e più economici rispetto ai residenziali per il traffico massiccio di build e test. Se invece il vostro pipeline verifica il comportamento del sito con l'IP reale di un utente di una specifica regione, è meglio utilizzare proxy residenziali — riducono il rischio di blocco per IP "da data center".
Checklist prima del deploy
- Le variabili sono impostate sia in maiuscolo che in minuscolo contemporaneamente
- Login e password nell'URL del proxy sono URL-codificati tramite
urllib.parse.quoteoencodeURIComponent - NO_PROXY contiene sia il formato di dominio che quello wildcard per la compatibilità tra gli strumenti
- In docker-compose, NO_PROXY include i nomi dei servizi dalla rete Compose, e non solo localhost
- Per Docker build, sono stati passati build-args separatamente dall'ambiente di runtime tramite ENV nel Dockerfile
- Secrets con l'URL del proxy in CI sono mascherati e non stampati nei log di debug
- Per le applicazioni Java, il proxy è impostato tramite proprietà di sistema -D, e non solo tramite env
- Verificata la protezione da httpoxy — l'intestazione Proxy dal client è azzerata a livello di server web
Conclusione
Le variabili HTTP_PROXY, HTTPS_PROXY e NO_PROXY sono una semplice convenzione che, nella pratica, si rompe a causa del case delle lettere, della sintassi delle eccezioni, dei confini tra le fasi di Docker build e Docker run, dell'ereditarietà dell'ambiente in docker-compose, delle fughe di segreti nei log CI e, infine, del fatto che non ogni libreria legge affatto queste variabili. Controllate ogni variabile isolatamente tramite curl e docker exec, prima di cercare bug nella logica aziendale dell'applicazione.
Se la vostra infrastruttura è legata al parsing di siti esterni, al test del geo-targeting della pubblicità o all'aggirare i blocchi per IP da un pipeline CI, è importante non solo passare correttamente le variabili d'ambiente, ma anche scegliere il tipo di proxy adatto per il compito. Per il test del traffico mobile e delle applicazioni che verificano il comportamento con operatori mobili, sono adatti i proxy mobili — forniscono IP di reti cellulari reali e raramente vengono bloccati dai filtri dei sistemi antifrode.