Nginx Server Block ile Çoklu Domain Yönetimi

Bir sunucuda onlarca web sitesi barındırmak zorunda kaldığında, her domain için ayrı bir sunucu kiralamak hem maliyetli hem de yönetimi zorlaştırır. Nginx’in server block özelliği tam da bu noktada devreye girer. Apache’deki virtual host mantığının Nginx karşılığı olan server block’lar, tek bir IP adresi üzerinden farklı domainlere farklı içerikler sunmanı sağlar. Bu yazıda gerçek dünya senaryolarıyla çoklu domain yönetimini baştan sona ele alacağız.

Server Block Nedir ve Nasıl Çalışır?

Nginx bir istek aldığında önce hangi server block’un bu isteği karşılayacağına karar verir. Bu karar iki temel kritere göre verilir: dinlenen port ve server_name direktifi. Bir kullanıcı tarayıcısına example.com yazdığında, DNS bu domain’i senin sunucunun IP’sine yönlendirir. Nginx gelen isteği alır, HTTP başlığındaki Host değerine bakar ve bu değerle eşleşen server block’u bularak isteği oraya iletir.

Bu mekanizma sayesinde example.com, blog.example.com, anotherdomain.com gibi onlarca farklı domain tek bir Nginx instance üzerinde çalışabilir. Kaynak kullanımı verimli olur, yönetim merkezileşir, SSL sertifikaları ayrı ayrı atanabilir.

Dizin Yapısını Anlamak

Nginx konfigürasyon dosyaları genellikle /etc/nginx/ dizini altında bulunur. Ubuntu/Debian tabanlı sistemlerde şu yapıyla karşılaşırsın:

/etc/nginx/
├── nginx.conf              # Ana konfigürasyon dosyası
├── sites-available/        # Tanımlı tüm site konfigürasyonları
├── sites-enabled/          # Aktif site konfigürasyonları (symlink)
├── conf.d/                 # Ek konfigürasyon dosyaları
└── snippets/               # Tekrar kullanılabilir konfigürasyon parçaları

sites-available dizininde tüm site konfigürasyonlarını tutarsın. Bir siteyi aktif etmek için sites-enabled dizinine sembolik link oluşturursun. Bu yaklaşım, bir siteyi geçici olarak devre dışı bırakmayı çok kolaylaştırır. Sadece symlink’i silmen yeterli, konfigürasyon dosyasına dokunmazsın.

CentOS/RHEL tabanlı sistemlerde ise sites-available ve sites-enabled yapısı varsayılan gelmez. Bunun yerine /etc/nginx/conf.d/ dizini kullanılır ve her .conf uzantılı dosya otomatik olarak yüklenir.

İlk Server Block Yapılandırması

Temel bir server block şu şekilde görünür:

server {
    listen 80;
    listen [::]:80;
    
    server_name example.com www.example.com;
    
    root /var/www/example.com/html;
    index index.html index.htm index.php;
    
    access_log /var/log/nginx/example.com.access.log;
    error_log /var/log/nginx/example.com.error.log;
    
    location / {
        try_files $uri $uri/ =404;
    }
}

Bu konfigürasyonu /etc/nginx/sites-available/example.com olarak kaydediyorsun. Ardından aktif etmek için sembolik link oluşturuyorsun:

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo mkdir -p /var/www/example.com/html
sudo chown -R www-data:www-data /var/www/example.com
sudo nginx -t
sudo systemctl reload nginx

nginx -t komutu konfigürasyonunu test eder. Hata olmadan geçerse reload yapabilirsin. Asla test etmeden reload yapma. Üretim sunucusunda bu alışkanlık seni ciddi kesintilerden korur.

Çoklu Domain Senaryosu

Diyelim ki aynı sunucuda üç farklı site barındıracaksın:

  • siteniz.com (kurumsal site)
  • blog.siteniz.com (blog alt domain)
  • musteri.com (tamamen farklı domain)

Her biri için ayrı konfigürasyon dosyası oluşturmak en temiz yaklaşımdır.

siteniz.com konfigürasyonu:

server {
    listen 80;
    listen [::]:80;
    
    server_name siteniz.com www.siteniz.com;
    
    root /var/www/siteniz.com/html;
    index index.html index.php;
    
    access_log /var/log/nginx/siteniz.com.access.log main;
    error_log /var/log/nginx/siteniz.com.error.log warn;
    
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
    
    location ~ .php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
    
    location ~ /.ht {
        deny all;
    }
}

blog.siteniz.com konfigürasyonu:

server {
    listen 80;
    listen [::]:80;
    
    server_name blog.siteniz.com;
    
    root /var/www/blog.siteniz.com/html;
    index index.html index.php;
    
    access_log /var/log/nginx/blog.siteniz.com.access.log;
    error_log /var/log/nginx/blog.siteniz.com.error.log;
    
    # WordPress permalink yapısı için
    location / {
        try_files $uri $uri/ /index.php?$args;
    }
    
    location ~ .php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
    }
    
    # Statik dosyalar için cache
    location ~* .(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
        expires 30d;
        add_header Cache-Control "public, no-transform";
    }
}

Wildcard ve Regex ile Server Name Kullanımı

Bazen tek tek domain yazmak yerine pattern matching kullanmak gerekir. Nginx bunu destekler:

# Wildcard - tüm alt domainleri yakalar
server {
    listen 80;
    server_name *.siteniz.com;
    
    root /var/www/default;
    
    location / {
        return 301 https://siteniz.com$request_uri;
    }
}

# Regex ile daha spesifik matching
server {
    listen 80;
    server_name ~^(www.)?(?<domain>.+)$;
    
    root /var/www/$domain/html;
    
    location / {
        try_files $uri $uri/ =404;
    }
}

Regex server name kullanırken başına ~ tilde işareti koyarsın. Nginx önce tam eşleşmeleri kontrol eder, sonra wildcard’ları, en son regex’leri. Bu öncelik sırasını bilmek bazı beklenmedik davranışların önüne geçer.

Default Server Block

Hiçbir server block ile eşleşmeyen istekler için default server tanımlamak iyi bir pratiktir. Bunu yapmaz isen Nginx konfigürasyon dosyasında ilk sıradaki server block tüm eşleşmeyen istekleri karşılar, bu da istemediğin durumlar yaratabilir:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    
    server_name _;
    
    # Bilinmeyen domainleri reddet
    return 444;
}

return 444 Nginx’e özel bir durum kodudur. Nginx bağlantıyı herhangi bir yanıt göndermeden kapatır. Bu özellikle bot’ların ve tarayıcıların rastgele IP üzerinden gelip sunucunu keşfetmesini engellemek için kullanışlıdır.

Alternatif olarak varsayılan bir sayfaya yönlendirebilirsin:

server {
    listen 80 default_server;
    server_name _;
    
    root /var/www/default;
    
    location / {
        return 301 https://anasayfam.com;
    }
}

SSL ile Server Block Yapılandırması

Bugünlerde SSL olmadan web sitesi işletmek düşünülemez. Let’s Encrypt ile ücretsiz SSL alabilirsin. Certbot kurulumu ve domain için sertifika alımı:

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d siteniz.com -d www.siteniz.com
sudo certbot --nginx -d blog.siteniz.com
sudo certbot --nginx -d musteri.com -d www.musteri.com

Certbot Nginx konfigürasyonunu otomatik güncelleyebilir. Ama ben kendi SSL konfigürasyonumu manuel yazmayı tercih ederim, çünkü ne olduğunu tam olarak görürsün:

# HTTP'den HTTPS'e yönlendirme
server {
    listen 80;
    listen [::]:80;
    server_name siteniz.com www.siteniz.com;
    
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }
    
    location / {
        return 301 https://$server_name$request_uri;
    }
}

# HTTPS server block
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    
    server_name siteniz.com www.siteniz.com;
    
    ssl_certificate /etc/letsencrypt/live/siteniz.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/siteniz.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/siteniz.com/chain.pem;
    
    # SSL güvenlik ayarları
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    
    # HSTS
    add_header Strict-Transport-Security "max-age=63072000" always;
    
    root /var/www/siteniz.com/html;
    index index.php index.html;
    
    access_log /var/log/nginx/siteniz.com.access.log;
    error_log /var/log/nginx/siteniz.com.error.log;
    
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
    
    location ~ .php$ {
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

Tekrar Kullanılabilir Konfigürasyon Parçaları (Snippets)

Birden fazla site yönettiğinde aynı konfigürasyon bloklarını tekrar tekrar yazmak hem zaman alır hem de tutarsızlıklara yol açar. Nginx’in snippets özelliği bu sorunu çözer.

/etc/nginx/snippets/ssl-params.conf dosyası oluştur:

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_stapling on;
ssl_stapling_verify on;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;

/etc/nginx/snippets/php-fpm.conf dosyası oluştur:

location ~ .php$ {
    try_files $uri =404;
    fastcgi_split_path_info ^(.+.php)(/.+)$;
    fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
    fastcgi_read_timeout 300;
    fastcgi_buffer_size 128k;
    fastcgi_buffers 4 256k;
}

location ~ /.ht {
    deny all;
}

Artık her server block’ta sadece include ediyorsun:

server {
    listen 443 ssl http2;
    server_name musteri.com www.musteri.com;
    
    ssl_certificate /etc/letsencrypt/live/musteri.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/musteri.com/privkey.pem;
    
    include snippets/ssl-params.conf;
    
    root /var/www/musteri.com/html;
    index index.php index.html;
    
    include snippets/php-fpm.conf;
    
    location / {
        try_files $uri $uri/ /index.php?$args;
    }
}

Upstream ve Proxy ile Uygulama Sunucularına Yönlendirme

Gerçek dünyada her domain statik dosya sunmaz. Node.js, Python (Gunicorn/Uvicorn), Ruby veya Java uygulamaları arka planda çalışıyor olabilir. Nginx bu durumda reverse proxy görevi üstlenir:

upstream nodejs_app {
    server 127.0.0.1:3000;
    server 127.0.0.1:3001;  # Load balancing için ikinci instance
    keepalive 32;
}

upstream python_app {
    server 127.0.0.1:8000;
    keepalive 16;
}

server {
    listen 443 ssl http2;
    server_name api.siteniz.com;
    
    ssl_certificate /etc/letsencrypt/live/api.siteniz.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.siteniz.com/privkey.pem;
    include snippets/ssl-params.conf;
    
    location / {
        proxy_pass http://nodejs_app;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
        proxy_read_timeout 86400;
    }
}

server {
    listen 443 ssl http2;
    server_name dashboard.siteniz.com;
    
    ssl_certificate /etc/letsencrypt/live/dashboard.siteniz.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/dashboard.siteniz.com/privkey.pem;
    include snippets/ssl-params.conf;
    
    location / {
        proxy_pass http://python_app;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Sık Karşılaşılan Sorunlar ve Çözümleri

Konfigürasyon testi başarısız olduğunda:

sudo nginx -t 2>&1 | head -20
# Hata satırını ve numarasını gösterir

# Konfigürasyon dosyasını parse et
sudo nginx -T | grep -A 20 "server_name siteniz.com"

Hangi server block’un aktif olduğunu anlamak:

# curl ile host header gönder
curl -H "Host: siteniz.com" http://localhost
curl -v --resolve siteniz.com:443:127.0.0.1 https://siteniz.com

# Nginx log'larından takip et
tail -f /var/log/nginx/siteniz.com.access.log
tail -f /var/log/nginx/siteniz.com.error.log

İzin sorunları:

# Web dizini sahipliğini düzenle
sudo chown -R www-data:www-data /var/www/siteniz.com
sudo chmod -R 755 /var/www/siteniz.com

# Nginx'in erişim haklarını kontrol et
sudo -u www-data ls /var/www/siteniz.com/html

Siteyi geçici devre dışı bırakmak:

# Symlink'i kaldır
sudo rm /etc/nginx/sites-enabled/siteniz.com
sudo systemctl reload nginx

# Siteyi tekrar aktif et
sudo ln -s /etc/nginx/sites-available/siteniz.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Performans için rate limiting

Çok sayıda domain yönetirken bazı domainler için istek sınırlaması yapmak isteyebilirsin. Özellikle login endpoint’leri veya API’lar için:

# nginx.conf içindeki http bloğuna ekle
http {
    limit_req_zone $binary_remote_addr zone=login:10m rate=10r/m;
    limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s;
}

Ardından server block içinde kullan:

server {
    server_name siteniz.com;
    
    location /wp-login.php {
        limit_req zone=login burst=5 nodelay;
        limit_req_status 429;
        
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
    
    location /api/ {
        limit_req zone=api burst=50;
        proxy_pass http://nodejs_app;
    }
}

Sertifika Yenileme Otomasyonu

Let’s Encrypt sertifikaları 90 günde bir yenilenmeli. Certbot bunu otomatik halleder ama kontrol etmek lazım:

# Mevcut cron veya systemd timer'ı kontrol et
sudo systemctl status certbot.timer
sudo certbot renew --dry-run

# Eğer manuel cron tercih edersen
sudo crontab -e
# Şunu ekle:
# 0 3 * * * certbot renew --quiet --post-hook "systemctl reload nginx"

Bir not: --post-hook ile yenileme sonrası Nginx’i reload etmez isen yeni sertifika diskde olur ama Nginx hala eski sertifikayı bellekte tutar.

Konfigürasyon Yönetimi için En İyi Pratikler

Onlarca domain yönetirken düzenli bir çalışma yöntemi belirlemelisin:

  • Her domain için ayrı dosya: sites-available/domain.com formatını kullan, aramayı ve yönetimi kolaylaştırır
  • Standart bir template belirle: Tüm yeni domainler için başlangıç şablonu hazırla, kopyala yapıştır yerine template kullan
  • Log rotasyonu: /etc/logrotate.d/nginx dosyasının doğru konfigüre edildiğinden emin ol, 10 domainlik log dosyaları diski doldurabilir
  • Konfigürasyonları git’e al: /etc/nginx/ dizinini git reposu haline getir, değişiklikleri commit et. Bir yanlış konfigürasyondan geri dönmek çok kolaylaşır
  • Test ortamı kur: Üretim sunucusuna uygulamadan önce staging’de dene
  • Yorum satırları ekle: Özellikle karmaşık location block’larına neyin neden yapıldığını yaz, 6 ay sonra kendin için de gerekli olur
# Konfigürasyonları git'e al
cd /etc/nginx
sudo git init
sudo git add .
sudo git commit -m "Initial nginx configuration"

# Değişiklik sonrası
sudo git add sites-available/yenidomain.com
sudo git commit -m "Add yenidomain.com server block"

Sonuç

Nginx server block’lar, tek sunucuda onlarca domaini yönetmenin en verimli yollarından biri. Bu yazıda temel yapılandırmadan SSL entegrasyonuna, snippet kullanımından reverse proxy konfigürasyonuna kadar gerçek dünyada karşılaşacağın senaryoları ele aldık.

En kritik nokta test alışkanlığı: her değişiklik sonrası nginx -t çalıştır, temiz geçerse reload et. Konfigürasyonları git ile versiyon kontrolüne al ve snippet’lerle tekrar kullanılabilir bloklar yarat. Zamanla kendi template kütüphaneni oluşturduğunda yeni bir domain eklemek dakikalar içinde halledilebilir bir iş haline gelir.

Sunucunda kaç domain çalışırsa çalışsın, bu yapıyı düzgün oturdurursan yönetim her zaman kontrol altında kalır.

Yorum yapın