Nginx konfigürasyonunu yönetirken en çok canımızı sıkan şeylerden biri, benzer ama hafifçe farklı davranışlar için tekrarlayan if blokları yazmak zorunda kalmaktır. Bir isteğin nereye yönlendirileceğini, hangi başlığın ekleneceğini ya da hangi backend’in kullanılacağını belirlemek için onlarca if yazıyorsunuz ve bir bakıyorsunuz konfigürasyon dosyası okunaksız bir hal almış. İşte bu noktada map modülü devreye giriyor ve işleri dramatik biçimde sadeleştiriyor.
map Modülü Nedir ve Neden Kullanmalısınız?
Nginx’in ngx_http_map_module modülü, bir değişkenin değerine göre başka bir değişken üretmenizi sağlar. Basitçe söylemek gerekirse, bir giriş değeri alır ve buna karşılık gelen bir çıkış değeri döndürür. Bunu bir anahtar-değer tablosuna bakıyor gibi düşünebilirsiniz.
if bloklarının aksine, map direktifi http bloğu seviyesinde tanımlanır ve sadece gerçekten ihtiyaç duyulduğunda değerlendirilir. Bu lazy evaluation yaklaşımı performans açısından büyük avantaj sağlar. Üstelik map direktifleri server veya location bloklarının içinde değil, dışında tanımlandığı için konfigürasyonunuz çok daha temiz görünür.
Nginx dokümantasyonunda ünlü bir uyarı vardır: “if is evil” (if kötüdür). Bu tam doğru değil elbette, ama if bloklarının bazı durumlarda beklenmedik davranışlar sergilediği gerçek. map ise bu tuzaklardan kaçınmanın en temiz yollarından biri.
Temel Sözdizimi
map direktifinin temel yapısı oldukça sezgiseldir:
http {
map $kaynak_degisken $hedef_degisken {
varsayilan_deger varsayilan_cikti;
eslesme_degeri1 cikti1;
eslesme_degeri2 cikti2;
}
}
Birkaç önemli nokta:
map: Her zamanhttpbloğu içinde,serverbloğunun dışında tanımlanır$kaynak_degisken: Eşleştirme yapılacak girdi değişkeni$hedef_degisken: Sonucun yazılacağı yeni değişkendefault: Hiçbir kural eşleşmediğinde kullanılacak değer
Şimdi gerçek dünyadan senaryolara geçelim.
Senaryo 1: Cihaz Tipine Göre Yönlendirme
E-ticaret platformlarında mobil ve masaüstü kullanıcılara farklı deneyimler sunmak yaygın bir ihtiyaçtır. User-Agent başlığına bakarak cihaz tipini tespit edebilir ve buna göre farklı upstream’lere yönlendirebilirsiniz:
http {
map $http_user_agent $mobil_mi {
default 0;
"~*mobile" 1;
"~*android" 1;
"~*iphone" 1;
"~*ipad" 1;
"~*tablet" 1;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://$mobil_mi == "1" ? mobile_backend : desktop_backend;
# Veya daha temiz kullanim:
set $backend "desktop_backend";
if ($mobil_mi) {
set $backend "mobile_backend";
}
proxy_pass http://$backend;
}
}
}
Daha temiz bir yaklaşım için map içinde doğrudan upstream adını üretmek daha iyidir:
http {
map $http_user_agent $backend_pool {
default desktop_backend;
"~*mobile" mobile_backend;
"~*android" mobile_backend;
"~*iphone" mobile_backend;
"~*ipad" tablet_backend;
}
upstream desktop_backend {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
}
upstream mobile_backend {
server 10.0.0.3:8080;
server 10.0.0.4:8080;
}
upstream tablet_backend {
server 10.0.0.5:8080;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://$backend_pool;
proxy_set_header X-Device-Type $backend_pool;
}
}
}
Bu yapıda User-Agent değeri otomatik olarak doğru backend’e yönlendiriyor. Yeni bir cihaz tipi eklemek istediğinizde sadece map bloğuna bir satır eklemeniz yeterli.
Senaryo 2: Ülkeye Göre İçerik Dili ve Yönlendirme
GeoIP modülüyle birlikte map kullanmak, coğrafi yönlendirme senaryolarında çok güçlü bir kombinasyon oluşturur. Diyelim ki $geoip_country_code değişkenine sahipsiniz:
http {
# Ulke koduna gore dil belirleme
map $geoip_country_code $site_dili {
default en;
TR tr;
DE de;
FR fr;
ES es;
AR ar;
SA ar;
AE ar;
}
# Ulke koduna gore para birimi
map $geoip_country_code $para_birimi {
default USD;
TR TRY;
DE EUR;
FR EUR;
GB GBP;
JP JPY;
}
# Bazi ulkeler icin ozel domain'e yonlendirme
map $geoip_country_code $yonlendirme_hedefi {
default "";
CN "https://cn.example.com";
RU "https://ru.example.com";
}
server {
listen 80;
server_name example.com;
# Yonlendirme gerekiyorsa gonder
if ($yonlendirme_hedefi) {
return 301 $yonlendirme_hedefi$request_uri;
}
location / {
proxy_pass http://app_backend;
proxy_set_header X-Site-Language $site_dili;
proxy_set_header X-Currency $para_birimi;
}
}
}
Burada önemli bir nokta: Birden fazla map bloğu tanımlayarak farklı amaçlar için farklı değişkenler üretiyorsunuz. Her map bağımsız çalışır ve sadece ihtiyaç duyulduğunda değerlendirilir.
Senaryo 3: Rate Limiting için Dinamik Sınırlar
API gateway senaryolarında farklı müşterilere farklı rate limit uygulamanız gerekebilir. Premium müşteriler daha yüksek limitlerden faydalanmalı:
http {
# API anahtarina gore rate limit zone secimi
map $http_x_api_key $rate_limit_zone {
default "standard_zone";
"premium_key_abc123" "premium_zone";
"premium_key_def456" "premium_zone";
"enterprise_key_xyz" "enterprise_zone";
"internal_service" "internal_zone";
}
# Rate limit zone'larini tanimla
limit_req_zone $binary_remote_addr zone=standard_zone:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=premium_zone:10m rate=100r/s;
limit_req_zone $binary_remote_addr zone=enterprise_zone:10m rate=1000r/s;
limit_req_zone $binary_remote_addr zone=internal_zone:10m rate=10000r/s;
server {
listen 80;
server_name api.example.com;
location /api/ {
limit_req zone=$rate_limit_zone burst=20 nodelay;
proxy_pass http://api_backend;
}
}
}
Bu yaklaşım tek tek müşterileri yönetmek için biraz kaba, ama burada asıl mesaj map ile dinamik zone seçimi yapılabildiği. Gerçek dünyada bunu daha çok müşteri tier’ına göre uygularsınız.
Senaryo 4: A/B Test Yönetimi
Canary deployment veya A/B test senaryolarında belirli kullanıcıları yeni versiyona yönlendirmek istersiniz. Cookie değerine göre bu kararı map ile verebilirsiniz:
http {
# A/B test cookie'sine gore backend secimi
map $cookie_ab_test $ab_backend {
default "production";
"variant_a" "production";
"variant_b" "canary";
"beta" "canary";
}
# Backend havuzlarini tanimla
upstream production {
server 10.0.1.1:8080 weight=9;
server 10.0.1.2:8080 weight=9;
}
upstream canary {
server 10.0.2.1:8080;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://$ab_backend;
add_header X-Served-By $ab_backend;
# Yoksa cookie set et
add_header Set-Cookie "ab_test=variant_a; Path=/; Max-Age=86400" always;
}
}
}
Daha gelişmiş bir yaklaşımda split_clients modülüyle birlikte kullanarak yüzdesel dağılım da yapabilirsiniz, ama map tabanlı yaklaşım cookie veya başlık değerine dayalı deterministik yönlendirme için mükemmeldir.
Senaryo 5: Güvenlik Başlıklarının Dinamik Yönetimi
HTTPS ve HTTP istekler için farklı güvenlik başlıkları, farklı ortamlar için farklı CORS politikaları… Bunları map ile çok temiz yönetebilirsiniz:
http {
# Izin verilen origin'lere gore CORS basligini ayarla
map $http_origin $cors_origin {
default "";
"https://app.example.com" "https://app.example.com";
"https://admin.example.com" "https://admin.example.com";
"https://partner.firma.com" "https://partner.firma.com";
"~^https://.*.example.com$" $http_origin;
}
# HTTPS mi HTTP mi?
map $scheme $hsts_baslik {
default "";
https "max-age=31536000; includeSubDomains; preload";
}
# Gelistirme ortami mi?
map $host $csp_politikasi {
default "default-src 'self'; script-src 'self'";
"dev.example.com" "default-src 'self' 'unsafe-inline' 'unsafe-eval'";
"staging.example.com" "default-src 'self' 'unsafe-inline'";
}
server {
listen 443 ssl;
server_name *.example.com;
location / {
proxy_pass http://app_backend;
# Kosullu CORS basliklarini ekle
if ($cors_origin) {
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
}
# Dinamik HSTS
if ($hsts_baslik) {
add_header Strict-Transport-Security $hsts_baslik always;
}
# Ortama ozgu CSP
add_header Content-Security-Policy $csp_politikasi always;
}
}
}
Senaryo 6: Log Formatını Dinamik Olarak Ayarlama
Bazı endpoint’lerin çok daha detaylı loglanması, bazılarının ise log dosyasını şişirmemesi için hiç loglanmaması gerekebilir. Özellikle health check endpoint’leri buna güzel bir örnek:
http {
# Bazi path'leri loglama
map $request_uri $log_yap {
default 1;
"~^/health" 0;
"~^/ping" 0;
"~^/metrics" 0;
"~^/favicon.ico" 0;
"~^/robots.txt" 0;
"~*.(css|js|png|jpg|gif)" 0;
}
# Hassas endpoint'ler icin farkli log format
map $request_uri $log_formati {
default "standart";
"~^/api/auth" "guvenlik";
"~^/api/payment" "guvenlik";
"~^/admin" "guvenlik";
}
log_format standart '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
log_format guvenlik '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time upstream_rt=$upstream_response_time '
'x_forwarded_for="$http_x_forwarded_for"';
server {
listen 80;
server_name example.com;
access_log /var/log/nginx/access.log standart if=$log_yap;
access_log /var/log/nginx/security.log guvenlik if=$log_formati;
location / {
proxy_pass http://app_backend;
}
}
}
Bu konfigürasyon sayesinde saniyede binlerce health check isteği log dosyanızı kirletmez ve güvenlik açısından kritik endpoint’ler ayrı bir log dosyasına yazılır.
Gelişmiş map Özellikleri
İç İçe map Kullanımı
Bir map çıktısını başka bir map‘e girdi olarak verebilirsiniz. Bu zincirleme yaklaşım karmaşık mantığı basamaklara bölmenizi sağlar:
http {
# Once tarayici ailesini belirle
map $http_user_agent $tarayici_ailesi {
default "other";
"~*MSIE" "ie";
"~*Trident" "ie";
"~*Firefox" "firefox";
"~*Chrome" "chrome";
"~*Safari" "safari";
}
# Sonra tarayici ailesine gore desteklenen ozellikleri belirle
map $tarayici_ailesi $webp_destegi {
default 1;
ie 0;
safari 0;
other 0;
}
map $tarayici_ailesi $http2_push_aktif {
default 1;
ie 0;
other 0;
}
server {
listen 443 ssl http2;
server_name example.com;
location ~* .(jpg|jpeg|png)$ {
# WebP destegi varsa WebP versiyonunu sun
set $resim_uzantisi "jpg";
if ($webp_destegi) {
set $resim_uzantisi "webp";
}
try_files $uri.$resim_uzantisi $uri =404;
}
}
}
Regex Eşleştirme
map direktifinde ~ (büyük/küçük harf duyarlı) ve ~* (büyük/küçük harf duyarsız) regex kullanabilirsiniz. Eşleştirme sırası şöyledir:
- Tam eşleşmeler önce kontrol edilir
~ve~*ile başlayan regex’ler sırayla kontrol edilirdefaulten son devreye girer
http {
map $uri $cache_suresi {
default "no-cache";
"~*.(css|js)$" "public, max-age=31536000, immutable";
"~*.(png|jpg|jpeg|gif|webp|svg)$" "public, max-age=2592000";
"~*.(woff|woff2|ttf|eot)$" "public, max-age=31536000";
"~^/api/" "no-store, no-cache";
"/index.html" "no-cache, must-revalidate";
}
server {
listen 80;
server_name example.com;
location / {
root /var/www/html;
add_header Cache-Control $cache_suresi;
try_files $uri $uri/ /index.html;
}
}
}
hostnames Parametresi
map bloğuna hostnames parametresi ekleyerek server_name mantığına benzer wildcard domain eşleştirmesi yapabilirsiniz:
http {
map $host $site_ortami {
hostnames;
default production;
"*.dev.example.com" development;
"*.staging.example.com" staging;
"localhost" development;
"127.0.0.1" development;
}
map $site_ortami $hata_ayrinti {
default 0;
development 1;
staging 1;
}
server {
listen 80;
server_name *.example.com localhost;
location / {
proxy_pass http://app_backend;
proxy_set_header X-Environment $site_ortami;
proxy_set_header X-Show-Debug $hata_ayrinti;
}
}
}
volatile ve include Kullanımı
Büyük projelerde map değerlerini harici dosyalara taşımak konfigürasyonu yönetilebilir kılar. Özellikle IP beyaz listeleri, API anahtarları gibi sık değişen veriler için idealdir:
http {
# Buyuk IP listelerini disari al
map $remote_addr $ip_izinli {
default 0;
include /etc/nginx/maps/izinli_ipler.map;
include /etc/nginx/maps/engellenen_ipler.map;
}
}
/etc/nginx/maps/izinli_ipler.map dosyasının içeriği:
# Bu dosya otomatik guncellenir, elle duzenlemeyin
10.0.0.0/8 1;
192.168.1.100 1;
172.16.0.0/12 1;
Bu yaklaşımla IP listelerini bir script ile güncelleyip nginx -s reload yapabilirsiniz. Ana konfigürasyon dosyasına dokunmanıza gerek kalmaz.
Yaygın Hatalar ve Dikkat Edilmesi Gerekenler
Nginx map kullanırken sıklıkla karşılaşılan bazı sorunlar var:
mapbloğunuserveriçine koymak:mapdirektifi mutlakahttpbloğu seviyesinde olmalı.serverveyalocationiçine koyarsanız Nginx başlamaz.
- Boş string ile
defaultkarışıklığı:default ""tanımladığınızda değişken var ama boş, tanımlamadığınızda değişken hiç set edilmemiş demektir.ifbloklarında bu iki durum farklı davranabilir.
- Regex performansı: Çok sayıda regex içeren
mapbloklarında performans düşebilir. Mümkünse tam eşleşme kullanın, regex’leri kaçınılmaz durumlara saklayın.
- Değişken adı çakışmaları: Mevcut Nginx değişkenlerinin adını
mapçıktısı için kullanmayın.$host,$urigibi yerleşik değişkenler var ve bunları ezmek beklenmedik sonuçlar doğurur.
Konfigürasyonu test etmek için her zaman:
nginx -t
komutunu çalıştırın. map sözdizimi hatalarını bu şekilde yakalayabilirsiniz. Daha ayrıntılı debug için:
nginx -T | grep -A 20 "map $"
komutu tüm include’ları açarak birleşik konfigürasyonu gösterir.
Performans Notları
map modülünün lazy evaluation özelliği Nginx’in neden bu direktifi tercih ettiğini anlatır. Bir request geldiğinde tüm map blokları hemen değerlendirilmez. Bir map değişkeni ilk kez kullanıldığı anda hesaplanır ve o request boyunca cache’lenir. Tanımladığınız ama hiç kullanmadığınız map değişkenleri herhangi bir CPU zamanı harcamaz.
Bununla birlikte büyük map blokları için Nginx hash tablosu boyutunu ayarlamak gerekebilir:
http {
map_hash_bucket_size 128;
map_hash_max_size 4096;
# Cok sayida giris iceren map
map $http_x_customer_id $musteri_plani {
default "free";
include /etc/nginx/maps/musteri_planlari.map;
}
}
map_hash_bucket_size değerini artırmak uzun string değerleri olan map blokları için gereklidir. Nginx bu konuda uyarı verir, log dosyalarını takip edin.
Sonuç
Nginx map modülü, web sunucusu konfigürasyonunuzu gereksiz if bloklarından arındırmanın ve dinamik davranışları temiz bir şekilde ifade etmenin en etkili yollarından biri. Cihaz tespiti, coğrafi yönlendirme, A/B test yönetimi, güvenlik başlıkları, log optimizasyonu gibi birbirinden farklı onlarca kullanım alanı var.
Özellikle şunu vurgulamak isterim: map sadece teknik bir araç değil, aynı zamanda bir mimari yaklaşım. Konfigürasyonunuzdaki karar mantığını veri olarak ifade edebiliyorsunuz. Bir müşterinin planını değiştirmek için konfigürasyon mantığına dokunmak yerine sadece map dosyasını güncelleyebiliyorsunuz. Bu ayrım, uzun vadede hem bakım kolaylığı hem de hata riskinin azalması anlamına geliyor.
Projenizde if bloklarından oluşan uzun zincirler görüyorsanız, büyük ihtimalle bunların önemli bir kısmı map ile çok daha temiz yazılabilir. Nginx dokümantasyonunu karıştırmak yerine mevcut konfigürasyonunuzu gözden geçirip dönüştürmeye başlayın. Birkaç saatlik bir çalışmayla hem okunabilirliği artırabilir hem de kendinize ileride çok teşekkür edeceksiniz.