IIS’te Birden Fazla Web Sitesi Barındırma: Host Header Kullanımı
Bir sunucuda onlarca web sitesi barındırmak, doğru yapılandırılmadığında gerçek bir kaos ortamına dönüşebilir. Özellikle IIS ile çalışan ortamlarda “host header” kavramını tam anlamıyla kavramadan kurulum yapmaya kalktığınızda, siteler birbirine karışır, yanlış içerik servis edilir ve hata ayıklamak için saatler harcarsınız. Bu yazıda IIS üzerinde birden fazla web sitesini tek bir IP adresi üzerinden nasıl doğru şekilde barındırabileceğinizi, host header konfigürasyonunun püf noktalarını ve production ortamında karşılaştığım gerçek senaryoları aktaracağım.
Host Header Nedir ve Neden Önemlidir?
HTTP/1.1 standardıyla hayatımıza giren Host header, bir istemcinin hangi web sitesini istediğini sunucuya bildirdiği mekanizmadır. Tarayıcınız www.siteniz.com adresine bağlandığında, TCP bağlantısı kurulduktan sonra sunucuya şunu söyler: “Ben www.siteniz.com için içerik istiyorum.” Sunucu da bu bilgiyi kullanarak doğru web sitesinin içeriğini döndürür.
IIS bu mekanizmayı site bindings (site bağlamaları) aracılığıyla yönetir. Her web sitesinin bir veya birden fazla binding’i olabilir ve her binding üç bileşenden oluşur:
- IP adresi: Hangi IP üzerinden dinleneceği (genellikle
*ile tüm IP’ler) - Port: Hangi port üzerinden dinleneceği (HTTP için 80, HTTPS için 443)
- Host header: Hangi domain adı için içerik servis edileceği
Tek bir sunucuda 50 farklı web sitesi barındırıyorsanız ve hepsine 80. porttan erişilmesi gerekiyorsa, bu üç bileşenin kombinasyonunun her site için benzersiz olması şarttır. İşte host header burada devreye girer.
IIS Üzerinde Yeni Web Sitesi Oluşturma
IIS Manager üzerinden site oluşturmak kolaydır, ancak ben komut satırı üzerinden yapılan konfigürasyonu tercih ederim çünkü tekrarlanabilir, scriptlenebilir ve daha az hata yapmaya açıktır. appcmd.exe ve PowerShell, IIS yönetiminde iki güçlü araçtır.
Önce temel bir site oluşturalım:
# PowerShell ile yeni web sitesi oluşturma
New-WebSite -Name "firma-web" `
-PhysicalPath "C:inetpubwwwrootfirma-web" `
-HostHeader "www.firmam.com" `
-Port 80 `
-IPAddress "*"
Bu komut www.firmam.com adresine gelen istekleri C:inetpubwwwrootfirma-web dizinine yönlendirir. Şimdi aynı sunucuya ikinci bir site ekleyelim:
# İkinci siteyi oluşturma
New-WebSite -Name "diger-firma" `
-PhysicalPath "C:inetpubwwwrootdiger-firma" `
-HostHeader "www.digerfirma.com" `
-Port 80 `
-IPAddress "*"
Dikkat edin, her iki site de aynı IP (*) ve aynı port (80) üzerinden dinliyor. IIS bu iki isteği birbirinden ayırt edebiliyor çünkü host header farklı.
appcmd ile Binding Yönetimi
appcmd.exe özellikle eski IIS sürümleriyle çalışırken veya batch scriptler yazarken hâlâ oldukça kullanışlıdır. IIS 7.0’dan itibaren mevcut olan bu araç, %systemroot%system32inetsrv dizininde bulunur.
# appcmd ile mevcut siteleri listeleme
%windir%system32inetsrvappcmd list site
# Belirli bir sitenin binding bilgilerini görüntüleme
%windir%system32inetsrvappcmd list site "firma-web" /text:*
Mevcut bir siteye ek host header eklemek istediğinizde şu yaklaşımı kullanabilirsiniz:
# Mevcut siteye ek binding ekleme (hem www hem de www'suz erişim için)
%windir%system32inetsrvappcmd set site "firma-web" /+bindings.[protocol='http',bindingInformation='*:80:firmam.com']
Bu adım çok sık atlanan bir noktadır. www.firmam.com için binding oluşturdunuz ama firmam.com (www’suz) için ayrıca binding eklemeyi unuttuysanız, ziyaretçilerin bir kısmı sitenize ulaşamaz veya IIS Default Web Site’a düşer, ki bu hiç istemediğiniz bir durumdur.
PowerShell ile Toplu Binding Yönetimi
Diyelim ki 20 farklı müşteriye ait web sitesini tek bir IIS sunucusunda barındırıyorsunuz. Her birini tek tek IIS Manager’dan eklemek yerine, bir CSV dosyasından okuyarak otomatik kurulum yapabilirsiniz.
Önce bir CSV dosyası oluşturun (siteler.csv):
# siteler.csv içeriği
SiteName,PhysicalPath,HostHeader,Port
musteri-a,"C:Sitesmusteri-a","www.musteri-a.com",80
musteri-b,"C:Sitesmusteri-b","www.musteri-b.com",80
musteri-c,"C:Sitesmusteri-c","www.musteri-c.com",80
Ardından PowerShell scripti ile toplu kurulum:
# Toplu site oluşturma scripti
Import-Module WebAdministration
$siteler = Import-Csv -Path "C:Scriptssiteler.csv"
foreach ($site in $siteler) {
# Fiziksel dizin yoksa oluştur
if (-not (Test-Path $site.PhysicalPath)) {
New-Item -ItemType Directory -Path $site.PhysicalPath -Force
Write-Host "Dizin olusturuldu: $($site.PhysicalPath)"
}
# Site zaten var mı kontrol et
if (Get-Website -Name $site.SiteName -ErrorAction SilentlyContinue) {
Write-Host "Site zaten mevcut: $($site.SiteName), atlanıyor..."
continue
}
# Siteyi oluştur
New-WebSite -Name $site.SiteName `
-PhysicalPath $site.PhysicalPath `
-HostHeader $site.HostHeader `
-Port $site.Port `
-IPAddress "*"
Write-Host "Site olusturuldu: $($site.SiteName) -> $($site.HostHeader)"
}
Bu script, production ortamında onlarca müşteri sitesini hızla devreye almanızı sağlar. Ayrıca idempotent bir yapıya sahip olduğundan, bir hata durumunda yeniden çalıştırdığınızda var olan siteleri tekrar oluşturmaya çalışmaz.
HTTPS Sitelerde SNI Kullanımı
HTTP sitelerde host header her zaman sorunsuz çalışır çünkü sunucu isteği aldıktan sonra hangi siteye yönleneceğini anlayabilir. Ancak HTTPS’de durum biraz daha karmaşıktır. TLS handshake, HTTP isteğinden önce gerçekleşir, yani sunucu hangi sertifikayı kullanacağına karar vermeden önce host header’ı göremez.
Bu sorunu çözmek için SNI (Server Name Indication) kullanılır. SNI, TLS handshake sırasında istemcinin hedef sunucu adını açıkça bildirmesine olanak tanır.
IIS 8.0 ve üzeri sürümlerde SNI desteği mevcuttur. Bir HTTPS binding’i SNI ile şu şekilde oluşturabilirsiniz:
# SNI etkin HTTPS binding oluşturma
# Önce sertifikayı kontrol edelim
$cert = Get-ChildItem -Path Cert:LocalMachineMy |
Where-Object { $_.Subject -like "*firmam.com*" } |
Select-Object -First 1
if ($cert) {
# Yeni HTTPS sitesi oluştur
New-WebSite -Name "firma-web-ssl" `
-PhysicalPath "C:Sitesfirma-web" `
-HostHeader "www.firmam.com" `
-Port 443 `
-IPAddress "*" `
-Ssl
# SNI ile sertifika bağlama
New-WebBinding -Name "firma-web-ssl" `
-Protocol "https" `
-Port 443 `
-HostHeader "www.firmam.com" `
-SslFlags 1
# Sertifikayı binding'e ata
$binding = Get-WebBinding -Name "firma-web-ssl" -Protocol "https"
$binding.AddSslCertificate($cert.Thumbprint, "My")
Write-Host "SNI etkin HTTPS binding olusturuldu"
} else {
Write-Host "Sertifika bulunamadı!" -ForegroundColor Red
}
-SslFlags 1 parametresi SNI’yi aktif eder. Eski IIS sürümlerinde (IIS 7.x) her HTTPS sitesi için ayrı bir IP adresi gerekirdi. SNI sayesinde tek IP üzerinden onlarca HTTPS sitesi barındırabiliyoruz.
Gerçek Dünya Senaryosu: Shared Hosting Ortamı
Birkaç yıl önce yaklaşık 80 müşteriye hizmet veren bir shared hosting ortamını yönetiyordum. Tek bir Windows Server 2019 üzerinde 80 farklı web sitesi vardı ve hepsinin hem HTTP hem de HTTPS üzerinden erişilebilir olması gerekiyordu.
Bu ortamda karşılaştığım en büyük sorun, yeni bir müşteri eklendiğinde mevcut sitelerin binding konfigürasyonlarının bazen bozulmasıydı. Bir süre sonra şu monitoring scriptini yazdım ve görev zamanlayıcıya ekledim:
# IIS binding tutarsizlik kontrolü
Import-Module WebAdministration
$sorunluSiteler = @()
foreach ($site in Get-Website) {
$bindings = Get-WebBinding -Name $site.Name
foreach ($binding in $bindings) {
$bilgi = $binding.bindingInformation
$parcalar = $bilgi.Split(":")
if ($parcalar.Count -eq 3) {
$hostHeader = $parcalar[2]
# Host header boş olan HTTP sitelerini tespit et
if ($binding.protocol -eq "http" -and [string]::IsNullOrEmpty($hostHeader)) {
$sorunluSiteler += [PSCustomObject]@{
SiteAdi = $site.Name
Protocol = $binding.protocol
Bilgi = $bilgi
Sorun = "Host header tanimsiz"
}
}
}
}
}
if ($sorunluSiteler.Count -gt 0) {
Write-Host "UYARI: Host header'siz siteler tespit edildi:" -ForegroundColor Yellow
$sorunluSiteler | ForEach-Object {
Write-Host " - $($_.SiteAdi): $($_.Sorun)" -ForegroundColor Red
}
# E-posta bildirimi gönder (SMTP ayarlarınıza göre düzenleyin)
# Send-MailMessage -To "[email protected]" -Subject "IIS Binding Uyarısı" ...
} else {
Write-Host "Tüm binding'ler düzgün görünüyor." -ForegroundColor Green
}
Bu script sayesinde, yanlış yapılandırılmış bir binding yüzünden müşteri sitesine erişilememeden önce sorunu tespit etmeyi başardım.
applicationHost.config Üzerinden Manuel Düzenleme
Bazen GUI veya PowerShell yerine doğrudan konfigürasyon dosyasına müdahale etmek gerekebilir. IIS’in ana konfigürasyon dosyası %SystemRoot%System32inetsrvconfigapplicationHost.config dosyasıdır. Bu dosyada site binding’leri şu şekilde görünür:
# applicationHost.config'de site binding yapısı örneği
# Dosyayı görüntülemek için:
notepad "%SystemRoot%System32inetsrvconfigapplicationHost.config"
# Ya da PowerShell ile binding bölümünü okumak için:
[xml]$config = Get-Content "$env:SystemRootSystem32inetsrvconfigapplicationHost.config"
$config.configuration.'system.applicationHost'.sites.site |
Select-Object name, @{N='Bindings';E={$_.bindings.binding.bindingInformation}} |
Format-Table -AutoSize
Bu dosyayı düzenlerken mutlaka yedek alın. Yanlış bir XML düzenlemesi IIS’in tamamen çalışmamasına neden olabilir. Ben her zaman şu satırla başlarım:
# Konfigürasyon dosyasını yedekle
$tarih = Get-Date -Format "yyyyMMdd-HHmmss"
Copy-Item "$env:SystemRootSystem32inetsrvconfigapplicationHost.config" `
"C:BackupapplicationHost-$tarih.config"
Write-Host "Yedek alındı: applicationHost-$tarih.config"
Sık Karşılaşılan Sorunlar ve Çözümleri
“HTTP Error 503: Service Unavailable” hatası
Bu genellikle application pool’un durduğu anlamına gelir. Site binding’lerini doğru yapsanız bile pool çalışmıyorsa siteye erişilemez.
# Tüm durdurulmuş application pool'ları başlatma
Import-Module WebAdministration
Get-WebConfiguration system.applicationHost/applicationPools/add |
Where-Object { $_.state -eq "Stopped" } |
ForEach-Object {
Start-WebAppPool -Name $_.name
Write-Host "Pool baslatildi: $($_.name)"
}
“HTTP Error 404: Not Found” hatası ama site çalışıyor
Bu durumda genellikle host header yanlış yazılmıştır ya da DNS henüz güncellenmemiştir. Test için hosts dosyasını kullanabilirsiniz:
# Windows hosts dosyasina test kaydı ekleme
# C:WindowsSystem32driversetchosts
# Dosyaya şu satırı ekleyin (Not Defteri'ni yönetici olarak açın):
# 192.168.1.100 www.firmam.com
# PowerShell ile hosts dosyasını düzenleme
$hostsPath = "C:WindowsSystem32driversetchosts"
$yeniKayit = "192.168.1.100 www.firmam.com"
Add-Content -Path $hostsPath -Value $yeniKayit
Write-Host "Hosts kaydı eklendi"
Birden fazla site aynı içeriği döndürüyor
Bu klasik bir Default Web Site sorunudur. IIS, gelen bir isteği hiçbir host header’la eşleştiremezse Default Web Site’a yönlendirir. Default Web Site’ın host header’ı genellikle boştur ve bu durum beklenmedik yönlendirmelere yol açar.
# Default Web Site'ın host header durumunu kontrol etme
Get-WebBinding -Name "Default Web Site" |
Select-Object protocol, bindingInformation |
Format-List
# Güvenlik için Default Web Site'ı durdurma (başka bir iş yapıyorsa dikkatli olun)
Stop-Website -Name "Default Web Site"
Write-Host "Default Web Site durduruldu"
IIS Loglarından Host Header Analizi
Hangi domain’e ne kadar istek geldiğini anlamak, kapasite planlaması ve sorun giderme açısından çok değerlidir. IIS logları varsayılan olarak C:inetpublogsLogFiles altında tutulur.
# Son 1 saatteki host header bazlı istek sayısını analiz etme
$logDizini = "C:inetpublogsLogFilesW3SVC1"
$sonLog = Get-ChildItem $logDizini -Filter "*.log" |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
if ($sonLog) {
Write-Host "Analiz edilen log: $($sonLog.Name)"
Get-Content $sonLog.FullName |
Where-Object { $_ -notlike "#*" } |
ForEach-Object {
$parcalar = $_ -split " "
if ($parcalar.Count -ge 8) {
$parcalar[8] # cs-host field (IIS W3C formatında genellikle 9. sütun)
}
} |
Group-Object |
Sort-Object Count -Descending |
Select-Object -First 20 |
ForEach-Object {
Write-Host "$($_.Count) istek: $($_.Name)"
}
} else {
Write-Host "Log dosyası bulunamadı" -ForegroundColor Red
}
Binding Önceliği ve Çakışma Durumları
IIS, gelen bir isteği eşleştirirken belirli bir öncelik sırası izler:
- Önce tam eşleşme aranır: IP adresi + port + host header
- Eşleşme bulunamazsa, IP adresi + port + boş host header kontrol edilir
- Yine eşleşme yoksa
*(tüm IP’ler) + port + host header denenir - Son çare olarak
*+ port + boş host header (yani Default Web Site benzeri yapılandırmalar) kullanılır
Bu öncelik sırasını bilmek, çakışma durumlarını anlamayı kolaylaştırır. İki sitenin aynı binding kombinasyonunu paylaşmasına IIS izin vermez, ancak bazen eski ve yeni binding’ler arasında beklenmedik çakışmalar olabilir. netsh komutuyla port 80’i dinleyen servisleri kontrol etmek faydalı olabilir:
# Port 80 üzerindeki dinleyicileri listeleme
netsh http show urlacl | findstr ":80"
# Tüm registered URL'leri görüntüleme
netsh http show urlacl
Sonuç
IIS üzerinde host header tabanlı çoklu site barındırma, kulağa karmaşık gelse de temel prensipler anlaşıldığında son derece yönetilebilir bir yapıya kavuşur. Dikkat edilmesi gereken kritik noktaları şöyle özetleyebilirim:
- Her sitenin host header’ının benzersiz olduğundan emin olun; hem
wwwhem dewww‘suz versiyonları binding listesine ekleyin. - HTTPS için SNI kullanımını zorunlu kılın; bu sayede tek IP üzerinden onlarca SSL sitesi barındırabilirsiniz.
- Default Web Site’ı ya tamamen durdurun ya da dikkatli bir şekilde yapılandırın; aksi hâlde tüm eşleşmeyen istekler buraya düşer.
- Önemli konfigürasyon değişikliklerinden önce her zaman
applicationHost.configyedeği alın. - PowerShell scriptleri ile binding yönetimini otomatize edin; hem hata riskini azaltır hem de tekrarlanabilir deployment süreçleri oluşturursunuz.
- Periyodik olarak binding tutarsızlıklarını kontrol eden monitoring scriptleri çalıştırın.
IIS, doğru yapılandırıldığında gerçekten güvenilir ve performanslı bir web sunucusudur. Sorunların büyük çoğunluğu yanlış veya eksik binding konfigürasyonundan kaynaklanır. Bu yazıdaki örnekleri kendi ortamınıza uyarlayarak, hem geliştirme hem de production süreçlerinizi çok daha sağlıklı bir temele oturtabilirsiniz.
