← Bloga geri dön

Linux, Docker ve CI'de HTTP_PROXY, HTTPS_PROXY, NO_PROXY Değişkenleri: 7 Yaygın Hata

Neden proxy ortam değişkenleri bir konteynerde çalışıyor ve diğerinde bozuluyor - 7 gerçek vaka ile çözümler ve kodları inceliyoruz.

📅7 Kasım 2026

Ortam değişkenleri HTTP_PROXY, HTTPS_PROXY ve NO_PROXY son derece basit görünüyor: bir değişken tanımladınız — tüm trafik proxy üzerinden gitti. Pratikte, tam da bu noktada geliştiriciler hata ayıklamak için saatler kaybediyor: proxy terminalde çalışıyor ama Docker konteynerinde çalışmıyor; curl proxy üzerinden çalışıyor, ama Python uygulaması çalışmıyor; CI koşucusu aniden iç registry'yi yasaklıyor çünkü NO_PROXY yanlış bir sözdizimi ile belirtilmiş. 7 gerçek tuzağı inceleyelim ve bunları somut kod ile nasıl aşacağımızı gösterelim.

Uygulamalar proxy değişkenlerini nasıl okur

Önemli olan: HTTP_PROXY, HTTPS_PROXY ve NO_PROXY — HTTP protokolünün standartları değil, her kütüphanenin kendi yöntemine göre uyguladığı bir konvansiyondur. Linux çekirdeği bu değişkenler hakkında hiçbir şey bilmez — bunları belirli bir uygulama veya kütüphane, ağ istemcisinin başlatılması aşamasında okur.

Örneğin, curl ve libcurl tabanlı araçlar, her işlem başlatıldığında değişkenleri okur. Python kütüphanesi requests, oturum oluşturulurken süreç ortamını tarayan urllib.request.getproxies() modülü aracılığıyla okur. Ancak Node.js'deki yerleşik fetch(), 18. sürüm öncesinde bu değişkenleri ek paketler olmadan dikkate almaz.

Buradan çıkan ilk kural: proxy değişkeni yalnızca belirli bir kütüphane tarafından açıkça destekleniyorsa çalışır. curl ile yapılan bir kontrol, Go, Java veya Node.js uygulamanızın aynı şekilde davranacağını garanti etmez.

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, ayrıntılı günlüğünde "Trying proxy.example.com..." gösterecektir.

Tuzağı 1: Değişkenlerin büyük/küçük harf kullanımı ve httpoxy açığı

Linux'taki çoğu konsol aracı, değişkenleri küçük harfle okur: http_proxy, https_proxy, no_proxy. Birçok modern SDK büyük harfe göre tasarlanmıştır. Fark küçük bir detay gibi görünebilir, ancak bu nedenle "yerel çalışıyor, sunucuda çalışmıyor" tarzında yarıdan fazlası hatalar, ayarların ilk gününde meydana gelir.

Ayrı bir konu — httpoxy açığı (CVE-2016-5385 ve ilgili olanlar). Temelinde, CGI ortamı (PHP, Python WSGI, bazı Ruby çerçeveleri) gelen istekteki Proxy HTTP başlığını otomatik olarak HTTP_PROXY ortam değişkenine dönüştürür. Bir saldırgan Proxy: http://attacker.com başlığını gönderirse, sunucu uygulamanız dışarıdan gelen istekleri başka bir proxy sunucusu üzerinden göndermeye başlar, bu da MITM saldırısına ve veri sızıntısına yol açar.

Çözüm — her iki harf durumunda da değişkenleri tanımlamak ve HTTP_PROXY'yi web sunucusu düzeyinde gelen istekler için açıkça sıfırlamaktır:

# Uyumluluk için her iki harf durumunda tanımlıyoruz
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"

# nginx'de — istemciden gelen Proxy başlığını engelliyoruz
proxy_set_header Proxy "";

Tuzağı 2: NO_PROXY sözdizimi araçlar arasında farklılık gösterir

NO_PROXY, bir istisna listesidir, ancak içinde alan adları ve maskelerin yazım formatı standartlaştırılmamıştır. Bazı araçlar .example.com'u "herhangi bir alt alan" olarak anlar, diğerleri *.example.com gerektirir, üçüncüsü ise yalnızca tam eşleşmeyi anlar.

Örneğin, Go kütüphanesi net/http, NO_PROXY'de CIDR notasyonunu destekler (10.0.0.0/8), ancak klasik curl belirli sürümlere kadar bunu desteklemez. Bu, aynı NO_PROXY'nin bir CI adımında iç registry'yi doğru bir şekilde hariç tutması ve diğer bir dilde yazılmış bir adımda hariç tutmaması durumuna yol açar.

Araç Alt alan için wildcard CIDR maskesi
curl / libcurl .example.com ile hayır
Go net/http .example.com ile evet
Python requests .example.com ile hayır
npm / Node.js *.example.com gereklidir hayır

Pratik bir tavsiye: NO_PROXY'de hem alan adı hem de wildcard biçimini aynı anda listeleyin, böylece farklı ayrıştırıcıları kapsarsınız:

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

Tuzağı 3: Docker build vs Docker run — farklı ortam değişkenleri

Bu, Docker'daki en yaygın tuzaklardan biri. docker run -e HTTP_PROXY=... ile tanımlanan değişkenler yalnızca konteyner çalışırken geçerlidir. Bunlar otomatik olarak imaj oluşturma aşamasına geçmez. Eğer Dockerfile'ınız içinde apt-get install veya pip install çağrısı yapıyorsa ve yapı kurumsal proxy üzerinden gidiyorsa, build zaman aşımında takılacaktır.

Yapı için, Docker'ın 1.9+ sürümünden itibaren "kutudan çıkar çıkmaz" anlayabileceği build-args'leri geçmelisiniz:

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 .

Değişkenin çalıştırılan konteyner içinde de erişilebilir olmasını istiyorsanız, onu Dockerfile'da ENV ile açıkça kopyalamalısınız — ARG ve ENV Docker'da temelde farklı mekanizmalardır ve ARG, katmanın oluşturulmasından hemen sonra kaybolur:

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

Bir diğer detay: Docker daemon, registry'den imajları indirmek için proxy kullanabilir — bu, Dockerfile'da değil, /etc/systemd/system/docker.service.d/http-proxy.conf systemd birimi yapılandırmasında ayarlanır, bu da genellikle konteyner içindeki değişkenlerle karıştırılır.

Tuzağı 4: docker-compose ve hizmetler arasında değişken mirası

docker-compose'da, ortam değişkenleri otomatik olarak konteynerlere aktarılmaz, eğer onları environment bölümünde açıkça belirtmediyseniz veya .env dosyasını kullanmıyorsanız. Sık yapılan bir hata, docker-compose up komutundan önce terminalde proxy'yi tanımlayıp, tüm hizmetlerin bunu almasını beklemektir.

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

NO_PROXY'ya dikkat edin: docker-compose ağları içinde konteynerler birbirlerine hizmet adları (db, redis) ile erişir, localhost ile değil. Eğer hizmet adlarını hariç tutmalara eklemeyi unutursanız, konteynerler arasındaki tüm iç trafik de dış proxy üzerinden geçer, bu da gecikmeleri artırır ve proxy iç protokolleri (PostgreSQL veya Redis gibi) geçirmiyorsa bağlantıyı bozabilir.

Tuzağı 5: CI/CD koşucuları ve proxy gizli bilgilerin sızması

GitHub Actions, GitLab CI ve Jenkins'de proxy değişkenleri genellikle koşucu veya organizasyon düzeyinde tanımlanır ve bu iki sorunu doğurur. Birincisi, proxy URL'si açık bir şekilde kullanıcı adı ve şifre içeriyorsa, bu, ortamı yazdıran herhangi bir komutla ( env, printenv, betiklerdeki hata ayıklama çıktısı) yapı günlüğüne düşer. İkincisi, kendi kendine barındırılan koşucular bazen CI işinin trafiği için tasarlanmamış olan ana sistem proxy'sini miras alır.

# .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: Bağlantıyı test et
        run: curl -sS https://api.example.com/health

Güvenlik kuralı: Proxy URL'sini yalnızca CI gizlilerinde saklayın (GitHub Secrets, GitLab CI/CD Variables "masked" bayrağı ile), ortam değişkenlerini hata ayıklama günlüklerinde asla tamamen yazdırmayın ve NO_PROXY'yi, CI platformuna (örneğin, github.com, gitlab.com) yapılan isteklerin proxy üzerinden gitmeyecek şekilde sınırlayın — aksi takdirde, proxy yoğunluk anında erişilemezse yapı takılabilir.

Tuzağı 6: Proxy kullanıcı adı ve şifresindeki özel karakterler

Eğer proxy şifresi @, :, / veya # karakterlerini içeriyorsa, URL ayrıştırıcısı dizgiyi yanlış yerlerde parçalara ayırır. Klasik bir hata: P@ssw0rd şifresi http://user:P@[email protected]:8080 dizisinde ayrıştırıcı, host'u "[email protected]" olarak okur, "proxy.com" olarak değil.

Çözüm — her zaman kullanıcı adı ve şifreyi değişken içine yerleştirmeden önce URL kodlaması yapmaktır:

# Python: proxy dizgisini oluştururken şifreyi kodlayın
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: encodeURIComponent ile benzer bir oluşturma
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;

Tuzağı 7: Ortam değişkenlerini göz ardı eden uygulamalar

Tüm HTTP istemcileri sistem proxy değişkenlerini dikkate almaz. Python'da requests kütüphanesi bunları otomatik olarak requests.Session() aracılığıyla okur, ancak bir kez açıkça proxies={} sözlüğünü çağrıya geçirdiyseniz, bu ortam değişkenlerini geçersiz kılar ve trafik doğrudan gidecektir, HTTP_PROXY sistemde tanımlı olsa bile.

import requests

# Boş bir proxies sözlüğü ortam değişkenlerini GEÇERSİZ KILAR
response = requests.get("https://api.example.com", proxies={})
# Doğru — proxy'yi açıkça geçirin veya proxies'i hiç geçmeyin
proxies = {
    "http": "http://user:[email protected]:8080",
    "https": "http://user:[email protected]:8080",
}
response = requests.get("https://api.example.com", proxies=proxies)

Node.js'de yerleşik fetch, ek kütüphaneler olmadan HTTP_PROXY'den haberdar değildir. undici ile ProxyAgent veya klasik https-proxy-agent'ı açıkça bağlamak gerekir:

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 da ortam değişkenleri yerine ayrı sistem özellikleri gerektirir: -Dhttp.proxyHost=proxy.example.com -Dhttp.proxyPort=8080 -Dhttps.proxyHost=proxy.example.com -Dhttps.proxyPort=8080. JVM'de HTTP_PROXY ortam değişkenleri varsayılan olarak hiç okunmaz.

Proxy değişkenlerini nasıl doğru test edersiniz

Uygulamanızı hata ayıklamak için zaman harcamadan önce, değişkeni ve proxy'yi koddan izole bir şekilde kontrol edin. Bu, saatler kazandırır.

# 1. Değişkenin mevcut oturumda gerçekten tanımlı olduğunu kontrol edin
env | grep -i proxy

# 2. Proxy'nin doğrudan çalıştığını curl ile kontrol edin
curl -x "$HTTP_PROXY" -v https://api.ipify.org

# 3. Konteynerin içinden, değişkenin ulaşıp ulaşmadığını kontrol edin
docker exec -it my_container env | grep -i proxy

# 4. NO_PROXY'nin gerçekten gerekli host'u hariç tuttuğunu kontrol edin
curl -v --noproxy "internal.corp" https://service.internal.corp/health

Eğer görev, dış API'lere toplu istekler, pazar yerlerinin ayrıştırılması veya CI boru hattından reklamların coğrafi hedeflemesini test etmekse, yük altında doğru proxy türünü seçmek önemlidir. CI'de genellikle yüksek yük senaryoları için yeterli olan datacenter proxy'leri — toplu yapı ve test trafiğinde daha hızlı ve daha ucuzdur. Eğer boru hattınız, belirli bir bölgedeki gerçek kullanıcı IP'si ile web sitesinin davranışını kontrol ediyorsa, rezidans proxy'leri kullanmak daha iyidir — bunlar "datacenter" IP'si nedeniyle engellenme riskini azaltır.

Dağıtım öncesi kontrol listesi

  • Değişkenler hem büyük hem de küçük harfle tanımlanmış
  • Proxy URL'sindeki kullanıcı adı ve şifre urllib.parse.quote veya encodeURIComponent ile URL kodlanmış
  • NO_PROXY, araçlar arasında uyumluluk için hem alan adı hem de wildcard formatını içeriyor
  • docker-compose'da NO_PROXY, yalnızca localhost değil, Compose ağındaki hizmet adlarını da içeriyor
  • Docker build için build-args, Dockerfile'da runtime env'den ayrı olarak geçilmiş
  • CI'deki proxy URL'si gizli bilgileri maskelemeli ve hata ayıklama günlüklerinde yazdırılmamalı
  • Java uygulamaları için proxy, yalnızca env üzerinden değil, sistem özellikleri -D ile tanımlanmalı
  • httpoxy'ye karşı koruma kontrol edilmiş — istemciden gelen Proxy başlığı web sunucusu düzeyinde sıfırlanmış

Sonuç

HTTP_PROXY, HTTPS_PROXY ve NO_PROXY değişkenleri — görünüşte basit bir konvansiyon, ancak pratikte karakter büyük/küçük harf kullanımı, istisna sözdizimi, Docker build ve Docker run arasındaki sınırlar, docker-compose'da ortam mirası, CI günlüklerinde gizli bilgilerin sızması ve nihayetinde, her kütüphanenin bu değişkenleri okuyup okumadığı gibi sorunlarla kırılmaktadır. Her bir değişkeni curl ve docker exec ile izole bir şekilde kontrol edin, uygulamanın iş mantığında hata aramadan önce.

Eğer altyapınız dış siteleri ayrıştırmaya, reklamların coğrafi hedeflemesini test etmeye veya CI boru hattından IP engellemelerini aşmaya bağlıysa, yalnızca ortam değişkenlerini doğru bir şekilde geçmekle kalmayıp, aynı zamanda göreve uygun proxy türünü seçmek de önemlidir. Mobil trafiği ve mobil operatörlerden gelen davranışları kontrol eden uygulamalar için mobil proxy'ler uygundur — bunlar gerçek mobil ağların IP'lerini sağlar ve daha az anti-fraud sistemlerinin filtrelerine takılır.