Variabel lingkungan HTTP_PROXY, HTTPS_PROXY, dan NO_PROXY terlihat sangat sederhana: menetapkan variabel — semua lalu lintas berjalan melalui proxy. Dalam praktiknya, di sinilah para pengembang menghabiskan berjam-jam untuk debugging: proxy berfungsi di terminal, tetapi tidak berfungsi di kontainer Docker; curl berjalan melalui proxy, tetapi aplikasi Python tidak; CI-runner tiba-tiba memblokir registry internal karena NO_PROXY ditentukan dengan sintaks yang salah. Mari kita bahas 7 jebakan nyata dan tunjukkan cara mengatasinya dengan kode konkret.
Bagaimana aplikasi membaca variabel proxy
Penting untuk dipahami: HTTP_PROXY, HTTPS_PROXY, dan NO_PROXY bukanlah standar protokol HTTP, melainkan konvensi yang diimplementasikan oleh setiap pustaka dengan cara mereka sendiri. Kernel Linux tidak mengetahui tentang variabel ini — mereka dibaca oleh aplikasi atau pustaka tertentu pada tahap inisialisasi klien jaringan.
Misalnya, curl dan utilitas berbasis libcurl membaca variabel setiap kali proses dijalankan. Pustaka Python requests membacanya melalui modul urllib.request.getproxies(), yang memindai lingkungan proses pada saat sesi dibuat. Sementara itu, fetch() bawaan di Node.js hingga versi 18 sama sekali tidak mempertimbangkan variabel ini tanpa paket tambahan.
Dari sini muncul aturan pertama: variabel proxy hanya berfungsi di tempat yang secara eksplisit didukung oleh pustaka tertentu. Memeriksa melalui curl tidak menjamin bahwa aplikasi Anda yang ditulis dalam Go, Java, atau Node.js akan berperilaku sama.
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 akan menunjukkan di log verbose: "Trying proxy.example.com..."
Jebakan 1: register variabel dan kerentanan httpoxy
Sebagian besar utilitas konsol di Linux membaca variabel dalam huruf kecil: http_proxy, https_proxy, no_proxy. Banyak SDK modern berorientasi pada huruf besar. Perbedaan ini tampak sepele, tetapi justru karena inilah setengah dari bug dengan gaya "berfungsi secara lokal, tidak berfungsi di server" terjadi pada hari pertama pengaturan.
Cerita terpisah adalah kerentanan httpoxy (CVE-2016-5385 dan yang terkait). Intinya adalah bahwa lingkungan CGI (PHP, Python WSGI, beberapa kerangka kerja Ruby) secara otomatis mengubah header HTTP Proxy dari permintaan masuk menjadi variabel lingkungan HTTP_PROXY. Penyerang mengirimkan header Proxy: http://attacker.com, dan aplikasi server Anda mulai mengirimkan permintaan keluar melalui server proxy pihak ketiga, yang membuka jalan untuk serangan MITM dan kebocoran data.
Solusinya adalah selalu menetapkan variabel dalam kedua huruf secara bersamaan dan secara eksplisit mengatur HTTP_PROXY menjadi kosong di tingkat server web untuk permintaan masuk:
# Menetapkan dalam kedua huruf untuk kompatibilitas
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"
# Di nginx — memblokir header Proxy dari klien
proxy_set_header Proxy "";
Jebakan 2: sintaks NO_PROXY berbeda antara alat
NO_PROXY adalah daftar pengecualian, tetapi format penulisan domain dan pola di dalamnya tidak distandarisasi. Beberapa alat memahami .example.com sebagai "subdomain mana pun", yang lain memerlukan *.example.com, dan yang lain hanya memahami kecocokan tepat dari host tanpa wildcard sama sekali.
Misalnya, pustaka Go net/http mendukung notasi CIDR dalam NO_PROXY (10.0.0.0/8), sementara curl klasik hingga versi tertentu — tidak. Ini menyebabkan situasi di mana NO_PROXY yang sama dengan benar mengecualikan registry internal untuk satu langkah CI dan tidak mengecualikan untuk yang lain yang ditulis dalam bahasa lain.
| Alat | Wildcard subdomain | CIDR-mask |
|---|---|---|
| curl / libcurl | melalui .example.com | tidak ada |
| Go net/http | melalui .example.com | ya |
| Python requests | melalui .example.com | tidak ada |
| npm / Node.js | memerlukan *.example.com | tidak ada |
Tips praktis: sebutkan dalam NO_PROXY baik format domain maupun format wildcard secara bersamaan untuk mencakup berbagai parser:
export NO_PROXY="localhost,127.0.0.1,.internal.corp,*.internal.corp,10.0.0.0/8"
Jebakan 3: Docker build vs Docker run — variabel lingkungan yang berbeda
Ini mungkin adalah jebakan paling umum di Docker. Variabel yang ditetapkan melalui docker run -e HTTP_PROXY=... hanya berlaku selama kontainer berjalan. Mereka TIDAK secara otomatis diteruskan ke tahap pembuatan image. Jika Dockerfile Anda memanggil apt-get install atau pip install, dan pembuatan berlangsung di belakang proxy perusahaan, build akan terjebak pada timeout.
Untuk membangun, Anda perlu mengirimkan build-args, yang dipahami Docker sejak versi 1.9+ "secara default" tanpa deklarasi di 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 .
Jika Anda ingin variabel tetap tersedia di dalam kontainer yang sedang berjalan, Anda perlu secara eksplisit menduplikasi melalui ENV dalam Dockerfile — ARG dan ENV di Docker adalah mekanisme yang secara prinsip berbeda, dan ARG menghilang segera setelah layer dibangun:
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
Satu lagi nuansa: daemon Docker itu sendiri dapat menggunakan proxy untuk mengunduh image dari registry — ini diatur bukan di Dockerfile, tetapi di konfigurasi unit systemd /etc/systemd/system/docker.service.d/http-proxy.conf, yang sering kali bingung dengan variabel di dalam kontainer.
Jebakan 4: docker-compose dan pewarisan variabel antar layanan
Di docker-compose, variabel lingkungan host TIDAK diteruskan ke kontainer secara otomatis, kecuali jika Anda secara eksplisit menuliskannya di bagian environment atau menggunakan file .env. Kesalahan umum adalah menetapkan proxy di terminal sebelum menjalankan docker-compose up dan mengharapkan semua layanan mengambilnya.
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
Perhatikan NO_PROXY: di dalam jaringan docker-compose, kontainer saling berkomunikasi berdasarkan nama layanan (db, redis), bukan localhost. Jika Anda lupa menambahkan nama layanan ke pengecualian, seluruh lalu lintas internal antara kontainer juga akan melewati proxy eksternal, yang dapat meningkatkan latensi dan mungkin merusak koneksi jika proxy tidak mengizinkan protokol internal seperti PostgreSQL atau Redis.
Jebakan 5: CI/CD runner dan kebocoran rahasia proxy
Di GitHub Actions, GitLab CI, dan Jenkins, variabel proxy sering ditetapkan di tingkat runner atau organisasi secara keseluruhan, dan ini menciptakan dua masalah. Pertama, jika URL proxy berisi login dan password dalam bentuk plaintext, ia akan muncul di log build pada setiap perintah yang mencetak lingkungan (env, printenv, output debug dalam skrip). Kedua, self-hosted runner kadang-kadang mewarisi proxy sistem host, yang tidak dirancang untuk lalu lintas 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: Uji koneksi
run: curl -sS https://api.example.com/health
Aturan keamanan: simpan URL proxy hanya dalam rahasia CI (GitHub Secrets, GitLab CI/CD Variables dengan tanda "masked"), jangan pernah mencetak variabel lingkungan secara keseluruhan di log debug, dan batasi NO_PROXY sehingga permintaan ke platform CI itu sendiri (misalnya, github.com, gitlab.com) tidak melewati proxy — jika tidak, build dapat terjebak jika proxy tidak tersedia pada saat beban puncak.
Jebakan 6: karakter khusus dalam login dan password proxy
Jika password proxy berisi karakter @, :, /, atau #, parser URL akan memecah string di tempat yang salah. Kesalahan klasik: password P@ssw0rd dalam string http://user:P@[email protected]:8080 dibaca oleh parser seolah host adalah "[email protected]", bukan "proxy.com".
Solusinya adalah selalu melakukan URL-encoding pada login dan password sebelum menyisipkannya ke dalam variabel:
# Python: encode password sebelum membangun string 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: membangun string serupa melalui 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;
Jebakan 7: aplikasi yang mengabaikan variabel lingkungan
Tidak semua klien HTTP menghormati variabel proxy sistem. Di Python, pustaka requests membacanya secara otomatis melalui requests.Session(), tetapi jika Anda sekali secara eksplisit mengirimkan kamus proxies={} dalam panggilan, ini akan menimpa variabel lingkungan dan lalu lintas akan berjalan langsung, bahkan jika HTTP_PROXY ditetapkan dalam sistem.
import requests
# Kamus kosong proxies MENGHENTIKAN variabel lingkungan
response = requests.get("https://api.example.com", proxies={})
# Benar — secara eksplisit mengirimkan proxy atau tidak mengirimkan proxies sama sekali
proxies = {
"http": "http://user:[email protected]:8080",
"https": "http://user:[email protected]:8080",
}
response = requests.get("https://api.example.com", proxies=proxies)
Di Node.js, fetch bawaan sama sekali tidak mengetahui tentang HTTP_PROXY tanpa pustaka tambahan. Anda perlu secara eksplisit mengimpor undici dengan ProxyAgent atau https-proxy-agent klasik:
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 juga memerlukan properti sistem terpisah alih-alih variabel lingkungan: -Dhttp.proxyHost=proxy.example.com -Dhttp.proxyPort=8080 -Dhttps.proxyHost=proxy.example.com -Dhttps.proxyPort=8080. Variabel lingkungan HTTP_PROXY di JVM secara default tidak dibaca sama sekali.
Cara menguji variabel proxy dengan benar
Sebelum menghabiskan waktu untuk debugging aplikasi, periksa variabel itu sendiri dan proxy secara terpisah dari kode. Ini menghemat berjam-jam.
# 1. Periksa bahwa variabel benar-benar diatur dalam sesi saat ini
env | grep -i proxy
# 2. Periksa fungsionalitas proxy secara langsung melalui curl
curl -x "$HTTP_PROXY" -v https://api.ipify.org
# 3. Periksa dari dalam kontainer bahwa variabel telah sampai
docker exec -it my_container env | grep -i proxy
# 4. Periksa bahwa NO_PROXY benar-benar mengecualikan host yang diperlukan
curl -v --noproxy "internal.corp" https://service.internal.corp/health
Jika tugasnya adalah permintaan massal ke API eksternal, pengambilan data dari marketplace, atau pengujian geo-targeting iklan dari pipeline CI, penting untuk memilih jenis proxy yang tepat untuk beban. Untuk skenario yang stabil dan berkapasitas tinggi dalam CI, biasanya cukup menggunakan proxy data center — mereka lebih cepat dan lebih murah dibandingkan dengan proxy residensial saat lalu lintas massal dari build dan pengujian. Namun, jika pipeline Anda memeriksa perilaku situs dari IP pengguna nyata di wilayah tertentu, lebih baik menggunakan proxy residensial — mereka mengurangi risiko pemblokiran berdasarkan IP "data center".
Checklist sebelum deploy
- Variabel ditetapkan baik dalam huruf besar maupun kecil secara bersamaan
- Login dan password dalam URL proxy URL-encoded melalui
urllib.parse.quoteatauencodeURIComponent - NO_PROXY mencakup format domain dan format wildcard untuk kompatibilitas antar alat
- Di docker-compose, NO_PROXY mencakup nama layanan dari jaringan Compose, bukan hanya localhost
- Untuk Docker build, build-args diteruskan terpisah dari runtime env melalui ENV di Dockerfile
- Rahasia dengan URL proxy di CI dimasking dan tidak dicetak di log debug
- Untuk aplikasi Java, proxy ditetapkan melalui properti sistem -D, bukan hanya melalui env
- Perlindungan terhadap httpoxy diperiksa — header Proxy dari klien diatur menjadi kosong di tingkat server web
Kesimpulan
Variabel HTTP_PROXY, HTTPS_PROXY, dan NO_PROXY — konvensi yang tampak sederhana, tetapi dalam praktiknya dapat rusak karena huruf besar/kecil, sintaks pengecualian, batas antara tahap Docker build dan Docker run, pewarisan lingkungan di docker-compose, kebocoran rahasia di log CI, dan akhirnya, tidak semua pustaka membaca variabel ini sama sekali. Periksa setiap variabel secara terpisah melalui curl dan docker exec sebelum mencari bug dalam logika bisnis aplikasi.
Jika infrastruktur Anda terikat pada pengambilan data dari situs eksternal, pengujian geo-targeting iklan, atau menghindari pemblokiran berdasarkan IP dari pipeline CI, penting untuk tidak hanya meneruskan variabel lingkungan dengan benar, tetapi juga memilih jenis proxy yang sesuai untuk tugas tersebut. Untuk pengujian lalu lintas seluler dan aplikasi yang memeriksa perilaku dari operator seluler, proxy seluler sangat cocok — mereka memberikan IP dari jaringan seluler nyata dan lebih jarang terjebak dalam filter sistem anti-fraud.