IIS’te ASP.NET Core Uygulaması Yayınlama
Yıllarca Linux tarafında çalıştıktan sonra bir müşteri projesi beni Windows Server ve IIS dünyasına çekti. “Ne kadar zor olabilir ki?” diye düşündüm. Ama ASP.NET Core uygulamasını IIS üzerinde yayınlamaya çalışırken karşılaştığım sorunlar bana oldukça değerli dersler verdi. Bu yazıda o dersleri sizinle paylaşıyorum; hem temel adımları hem de “ah bir de şu vardı” dedirten o kritik detayları.
IIS ve ASP.NET Core: Temel Kavramlar
Önce zihinsel modeli doğru kuralım. ASP.NET Core, IIS’in içine gömülü çalışmaz. Klasik ASP.NET’te uygulama doğrudan IIS worker process (w3wp.exe) içinde çalışırdı. ASP.NET Core’da ise iki seçenek var:
In-Process Hosting: Uygulama yine w3wp.exe içinde çalışır. Performans açısından daha avantajlı, çünkü network hop yok.
Out-of-Process Hosting: IIS bir reverse proxy gibi davranır, arkasında Kestrel server çalışır. HTTP isteklerini Kestrel’e iletir. Daha esnek ama bir miktar ek yük var.
Benim tavsiyem: Yeni projelerde In-Process ile başlayın, spesifik bir nedeniniz olmadıkça. Ama Out-of-Process’i de anlamak gerekiyor çünkü eski bir projeyi devraldığınızda karşınıza çıkabilir.
Bu mimarinin çalışabilmesi için sunucuda ASP.NET Core Hosting Bundle kurulu olması zorunlu. Bu bundle; .NET Runtime, ASP.NET Core Runtime ve IIS için gerekli olan ASP.NET Core Module (ANCM)‘ü içeriyor.
Ortamın Hazırlanması
Windows Server’da IIS Kurulumu
Eğer IIS henüz kurulu değilse, önce onu aktif edelim:
# PowerShell ile IIS ve gerekli bileşenleri kur
Install-WindowsFeature -Name Web-Server, Web-Asp-Net45, Web-Net-Ext45, Web-ISAPI-Ext, Web-ISAPI-Filter, Web-Mgmt-Console -IncludeManagementTools
Kurulum bittikten sonra inetmgr komutunu çalıştırarak IIS Manager açılıyorsa temel kurulum tamam demektir.
ASP.NET Core Hosting Bundle Kurulumu
Microsoft’un resmi download sayfasından ilgili .NET sürümü için Hosting Bundle’ı indirip kurabilirsiniz. Ama otomatik ve tekrarlanabilir kurulum için şunu tercih ediyorum:
# PowerShell ile Hosting Bundle indir ve kur (örnek: .NET 8)
$url = "https://dot.net/v1/dotnet-install.ps1"
# Ya da doğrudan MSI path'ini kullanarak sessiz kurulum:
Start-Process -Wait -FilePath "dotnet-hosting-8.0.x-win.exe" -ArgumentList "/quiet /norestart"
# Kurulumdan sonra IIS'i yeniden başlat
net stop was /y
net start w3svc
Önemli not: Hosting Bundle kurulduktan sonra IIS’i mutlaka yeniden başlatın. Ben bunu atladığımda uygulama havuzu sürekli hata verdi, yarım saat de boşa harcadım. IIS servisi yeniden başlamadan ANCM modülü yüklenmiyor.
Kurulumun başarılı olduğunu doğrulamak için:
# Kurulu .NET sürümlerini listele
dotnet --list-runtimes
# IIS modüllerinde ANCM'yi kontrol et
Get-WebConfiguration system.webServer/globalModules/* | Where-Object {$_.name -like "*AspNetCore*"}
Uygulamayı Publish Etmek
Uygulamayı IIS’e deploy etmeden önce publish etmemiz gerekiyor. Publish, uygulamayı bağımsız çalışabilir hale getiren adım.
# Framework-dependent publish (sunucuda .NET Runtime kurulu olduğu varsayımıyla)
dotnet publish -c Release -o C:inetpubappsmyapp
# Self-contained publish (sunucuda .NET kurulu olmasa bile çalışır)
dotnet publish -c Release -r win-x64 --self-contained true -o C:inetpubappsmyapp
# Tek dosyaya sıkıştırılmış publish
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -o C:inetpubappsmyapp
Ben genellikle framework-dependent publish tercih ediyorum. Sebep basit: Sunucuda .NET’i güncellemek, her uygulamayı ayrı ayrı güncellemekten çok daha kolay. Self-contained ise izolasyon istediğinizde, örneğin aynı sunucuda farklı .NET sürümü gerektiren iki uygulama varsa, işe yarıyor.
Publish sonrasında web.config dosyasının oluştuğunu kontrol edin:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<location path="." inheritInChildApplications="false">
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="dotnet"
arguments=".MyApp.dll"
stdoutLogEnabled="false"
stdoutLogFile=".logsstdout"
hostingModel="inprocess" />
</system.webServer>
</location>
</configuration>
Burada hostingModel="inprocess" yerine hostingModel="outofprocess" yazarsanız Out-of-Process moda geçersiniz.
IIS’te Site ve Application Pool Yapılandırması
Application Pool Oluşturma
ASP.NET Core için özel bir Application Pool açmak iyi pratik. Ve kritik nokta: Bu pool’un .NET CLR sürümü “No Managed Code” olmalı. Çünkü ASP.NET Core kendi runtime’ını yönetiyor, IIS’in klasik CLR’ına ihtiyacı yok.
# PowerShell ile Application Pool oluştur
Import-Module WebAdministration
New-WebAppPool -Name "MyAppPool"
Set-ItemProperty IIS:AppPoolsMyAppPool -Name managedRuntimeVersion -Value ""
Set-ItemProperty IIS:AppPoolsMyAppPool -Name managedPipelineMode -Value "Integrated"
# Pool'u 64-bit olarak ayarla
Set-ItemProperty IIS:AppPoolsMyAppPool -Name enable32BitAppOnWin64 -Value $false
# Otomatik başlatma
Set-ItemProperty IIS:AppPoolsMyAppPool -Name startMode -Value "AlwaysRunning"
AlwaysRunning modu önemli. Default olan OnDemand modda IIS belirli bir süre istek gelmezse pool’u uyutur. İlk istek geldiğinde “cold start” yaşanır ve kullanıcı yavaş bir yanıt alır. Production ortamda bunu istemezsiniz.
Web Sitesi Oluşturma
# Yeni site oluştur
New-Website -Name "MyApp" `
-PhysicalPath "C:inetpubappsmyapp" `
-ApplicationPool "MyAppPool" `
-Port 80 `
-HostHeader "myapp.example.com"
# HTTPS binding ekle (sertifika thumbprint'ini kullanarak)
$cert = Get-ChildItem -Path Cert:LocalMachineMy | Where-Object {$_.Subject -like "*myapp.example.com*"}
New-WebBinding -Name "MyApp" -Protocol "https" -Port 443 -HostHeader "myapp.example.com" -SslFlags 1
(Get-WebBinding -Name "MyApp" -Protocol "https").AddSslCertificate($cert.Thumbprint, "my")
Dosya Sistemi İzinleri
Bu konuda en çok sorun yaşadığım yerlerden biri. Application Pool identity’si publish klasörüne erişemezse uygulama 500 hatası verir ve neden anlamak zaman alır.
# Application Pool identity için izin ver
# Pool adı "MyAppPool" ise identity "IIS AppPoolMyAppPool" şeklinde
$appPath = "C:inetpubappsmyapp"
$acl = Get-Acl $appPath
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
"IIS AppPoolMyAppPool",
"ReadAndExecute,Write",
"ContainerInherit,ObjectInherit",
"None",
"Allow"
)
$acl.SetAccessRule($rule)
Set-Acl $appPath $acl
# Temp klasörü için de izin gerekebilir
$tempPath = "C:WindowsTemp"
# Ya da uygulama kendi temp klasörünü kullanıyorsa o klasöre de izin ver
Buradaki Write izninin sebebi: Uygulama log dosyaları yazıyor, data protection key’leri oluşturuyor, bazı durumlarda geçici dosyalar üretiyor. Sadece ReadAndExecute verirseniz bu operasyonlar başarısız olur.
Environment Variables ve appsettings Yönetimi
Farklı ortamlar için (Dev, Staging, Production) farklı konfigürasyonlar gerekiyor. IIS üzerinde bunu Application Pool veya Site düzeyinde environment variable ile yönetebilirsiniz.
# ASPNETCORE_ENVIRONMENT değişkenini site düzeyinde set et
Set-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST/MyApp" `
-filter "system.webServer/aspNetCore/environmentVariables" `
-name "." `
-value @{name="ASPNETCORE_ENVIRONMENT";value="Production"}
# Connection string gibi hassas bilgileri de environment variable olarak sakla
Set-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST/MyApp" `
-filter "system.webServer/aspNetCore/environmentVariables" `
-name "." `
-value @{name="ConnectionStrings__DefaultConnection";value="Server=...;Database=..."}
Tavsiye: Connection string’leri ve API key’leri asla appsettings.json içine koymayın, özellikle production’da. Environment variable, Windows Credential Manager veya Azure Key Vault gibi çözümleri tercih edin. Bu bir kural değil, acı deneyimlerin getirdiği bir alışkanlık.
Sorun Giderme: 500 Hatalarıyla Başa Çıkmak
IIS’te ASP.NET Core hataları genellikle ya 500.19 (web.config sorunu) ya 500.30 (uygulama başlatma hatası) ya da 502.5 (process başlatılamadı) şeklinde görünür. Her birinin farklı nedeni var.
İlk yapacağım şey stdout logları açmak:
<!-- web.config içinde stdoutLogEnabled="true" yap -->
<aspNetCore processPath="dotnet"
arguments=".MyApp.dll"
stdoutLogEnabled="true"
stdoutLogFile=".logsstdout"
hostingModel="inprocess" />
Loglar için logs klasörünü önceden oluşturun ve izin verin:
New-Item -ItemType Directory -Path "C:inetpubappsmyapplogs"
# Klasöre Application Pool identity'sine write izni ver
icacls "C:inetpubappsmyapplogs" /grant "IIS AppPoolMyAppPool:(OI)(CI)F"
Production’da stdout logları açık bırakmayın. Ciddi performans sorunu yaratır ve disk dolabilir. Sorunu bulduktan sonra hemen kapatın.
Windows Event Viewer da değerli bir kaynak. Application log’larında ve Microsoft-IIS-W3SVC-WP log’larında detaylı hata bilgisi bulabilirsiniz:
# PowerShell ile son IIS hatalarını çek
Get-EventLog -LogName Application -Source "IIS AspNetCore Module*" -Newest 20
Bir gerçek dünya senaryosu paylaşayım. Bir projede uygulama local’de mükemmel çalışıyor, IIS’e deploy ettiğimizde 502.5 veriyordu. Stdout loguna baktığımda şunu gördüm:
Application startup exception: System.IO.FileNotFoundException:
Could not load file or assembly 'SomePackage, Version=x.x.x.x'
Sorun self-contained publish yapmadan bazı native dependency’lerin eksik kalmasıydı. Çözüm framework-dependent yerine self-contained publish yapmak oldu. Basit bir sorundu ama bulmak 2 saat aldı.
HTTPS ve SSL Sertifika Yönetimi
Üretim ortamında HTTP’yi HTTPS’e yönlendirmek standart bir gereksinim. Bunu hem IIS düzeyinde hem de uygulama kodunda yapabilirsiniz.
Uygulama kodunda (Program.cs):
// HTTPS yönlendirmesi
app.UseHttpsRedirection();
// HSTS (HTTP Strict Transport Security)
app.UseHsts();
IIS düzeyinde URL Rewrite modülü ile HTTP’den HTTPS’e yönlendirme:
<!-- web.config içine ekle -->
<system.webServer>
<rewrite>
<rules>
<rule name="HTTP to HTTPS" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="off" ignoreCase="true" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent" />
</rule>
</rules>
</rewrite>
</system.webServer>
Let’s Encrypt sertifikası kullanıyorsanız, Windows üzerinde win-acme aracı işinizi kolaylaştırır. IIS entegrasyonu var ve otomatik yenileme yapıyor.
Deployment Otomasyonu
Her seferinde elle deploy yapmak zaman kaybı. CI/CD pipeline’ınız olmasa bile basit bir PowerShell script’iyle süreci otomatikleştirebilirsiniz:
# deploy.ps1 - Basit deployment scripti
param(
[string]$PublishPath = "C:buildsmyapp",
[string]$TargetPath = "C:inetpubappsmyapp",
[string]$SiteName = "MyApp",
[string]$AppPool = "MyAppPool"
)
Write-Host "Deployment basliyor..."
# IIS sitesini durdur
Stop-Website -Name $SiteName
Stop-WebAppPool -Name $AppPool
# Kısa bekle, process'ların kapanmasına izin ver
Start-Sleep -Seconds 3
# Eski dosyaları yedekle
$backupPath = "C:backupsmyapp_$(Get-Date -Format 'yyyyMMdd_HHmmss')"
Copy-Item -Path $TargetPath -Destination $backupPath -Recurse
# Yeni dosyaları kopyala (logs klasörünü atla)
robocopy $PublishPath $TargetPath /MIR /XD logs /XF *.log
# Application Pool'u başlat
Start-WebAppPool -Name $AppPool
Start-Sleep -Seconds 2
# Siteyi başlat
Start-Website -Name $SiteName
Write-Host "Deployment tamamlandi."
# Health check
$response = Invoke-WebRequest -Uri "http://localhost/health" -UseBasicParsing
if ($response.StatusCode -eq 200) {
Write-Host "Health check basarili!" -ForegroundColor Green
} else {
Write-Host "Health check basarisiz! Rollback yapiliyor..." -ForegroundColor Red
Stop-Website -Name $SiteName
Stop-WebAppPool -Name $AppPool
robocopy $backupPath $TargetPath /MIR /XD logs
Start-WebAppPool -Name $AppPool
Start-Website -Name $SiteName
}
Bu script basit ama işlevsel. Siteyi durduruyor, yedekliyor, yeni dosyaları kopyalıyor, başlatıyor ve health check yapıyor. Başarısız olursa rollback yapıyor. İyi bir başlangıç noktası.
Web Farm ve Yük Dengeleme Senaryoları
Birden fazla sunucuya deploy ediyorsanız dikkat etmeniz gereken birkaç konu var.
Data Protection Keys: ASP.NET Core, session, antiforgery token gibi şeyleri şifrelemek için Data Protection API kullanır. Birden fazla sunucu varsa bu key’lerin paylaşılı bir yerde tutulması gerekir, aksi takdirde sunucular birbirinin oluşturduğu token’ları validate edemez.
// Program.cs içinde shared key ring yapılandırması
builder.Services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(@"\shared-serverdp-keys"))
.SetApplicationName("MyApp");
ARR (Application Request Routing) ile Sticky Sessions: IIS üzerinde ARR kullanıyorsanız ve session tabanlı bir uygulamanız varsa sticky session (server affinity) açık olmalı. Yoksa aynı kullanıcının istekleri farklı sunuculara gidebilir ve session kaybolur.
İzleme ve Loglama
Production ortamında neyin döndüğünü bilmek zorundasınız. ASP.NET Core’un built-in loglama ile Windows Event Log entegrasyonu oldukça kullanışlı:
// Program.cs
builder.Logging.AddEventLog(settings =>
{
settings.SourceName = "MyApp";
});
// Serilog ile daha gelişmiş loglama
builder.Host.UseSerilog((context, config) =>
{
config
.WriteTo.EventLog("MyApp", manageEventSource: true)
.WriteTo.File(@"C:inetpubappsmyapplogsapp-.txt",
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 30);
});
IIS access logları varsayılan olarak C:inetpublogsLogFiles altında tutuluyor. Log rotation ve temizleme için bir görev oluşturmanızı öneririm, disk bu loglarla dolabilir.
Sonuç
IIS üzerinde ASP.NET Core yayınlamak ilk bakışta karmaşık görünse de adımları doğru izlediğinizde oldukça stabil çalışıyor. Özetleyecek olursam:
- Hosting Bundle kurulumu ve IIS restart’ı asla atlamayın
- Application Pool’u “No Managed Code” olarak yapılandırın
- In-Process hosting modern uygulamalar için tercih edilen seçenek
- Dosya sistemi izinleri sorunların büyük çoğunluğunun kaynağı; Application Pool identity’sine gerekli izinleri verin
- stdout loglarını sorun giderme sırasında açın, sonra kapatın
- AlwaysRunning modu ile cold start sorununu ortadan kaldırın
- Environment variable ile ortam bazlı konfigürasyon yönetimi yapın
Web farm senaryolarında Data Protection key paylaşımını, CI/CD süreçlerinde ise sağlıklı bir deployment scripti veya pipeline’ını kurmayı ihmal etmeyin. Bu temelleri sağlam kurduktan sonra IIS üzerinde ASP.NET Core uygulamaları çalıştırmak gerçekten keyifli hale geliyor.
