Nginx sıkıştırma ve statik önbellek ayarları, bir sitede en az emekle en çok kazanç veren iki ayardır. Sunucunuzda hiçbir uygulama kodunu değiştirmeden, yalnızca on beş satırlık bir yapılandırmayla 380 KB'lık bir CSS dosyasını 45 KB'a indirebilir, tekrar gelen ziyaretçinin sunucunuza hiç istek atmamasını sağlayabilirsiniz. Buna rağmen sahada gördüğüm kurulumların çoğunda gzip varsayılan hâlde bırakılmış, Cache-Control başlığı hiç gönderilmiyor ve ziyaretçi her sayfa yenilemesinde aynı 40 dosyayı baştan indiriyor.
Bu rehberde Nginx'te sıkıştırmayı doğru kurmayı, hangi MIME tiplerinin sıkıştırılması gerektiğini, Brotli'nin gzip'e göre ne kazandırdığını, expires ile Cache-Control arasındaki farkı, ETag'in ne zaman zarar verdiğini ve önceden sıkıştırılmış dosyaların nasıl servis edileceğini anlatacağım. Her ayarı çalışan yapılandırma bloklarıyla vereceğim ve sonuçları curl ile nasıl doğrulayacağınızı göstereceğim; çünkü doğrulanmayan bir performans ayarı, yapılmamış sayılır.
Sıkıştırma Neyi Çözer, Neyi Çözmez#
HTTP sıkıştırması, sunucunun yanıt gövdesini gönderirken küçültmesi ve tarayıcının açmasıdır. Metin tabanlı içerikte kazanç dramatiktir: HTML, CSS, JavaScript, JSON ve SVG dosyaları genellikle %70-85 oranında küçülür çünkü bu formatlar çok sayıda tekrar eden karakter dizisi barındırır. Buna karşılık JPEG, PNG, WebP, MP4 ve ZIP gibi zaten sıkıştırılmış formatlarda kazanç sıfıra yakındır; hatta sıkıştırma denemesi CPU harcadığı için net zarardır.
Bu ayrım sıkıştırma yapılandırmasının temel kuralını doğurur: yalnızca metin tiplerini sıkıştırın. Kazançları somut görmek için tipik bir varlık setine bakalım.
| Dosya tipi | Ham boyut | gzip | brotli | Sıkıştırılmalı mı |
|---|---|---|---|---|
| HTML | 90 KB | 14 KB | 11 KB | Evet |
| CSS | 380 KB | 48 KB | 40 KB | Evet |
| JavaScript | 620 KB | 170 KB | 145 KB | Evet |
| JSON API yanıtı | 120 KB | 12 KB | 10 KB | Evet |
| SVG | 45 KB | 9 KB | 7 KB | Evet |
| JPEG / WebP | 210 KB | 209 KB | 208 KB | Hayır |
| WOFF2 font | 28 KB | 28 KB | 28 KB | Hayır |
WOFF2 tablodaki en sık yapılan hatadır: bu format zaten Brotli ile sıkıştırılmıştır, tekrar sıkıştırmak boşuna CPU tüketir. Aynı şekilde .woff2 uzantısını gzip_types listesine eklemek yaygın bir kopyala-yapıştır hatasıdır.
gzip Yapılandırması: Satır Satır#
Nginx'te gzip varsayılan olarak kapalıdır ve açtığınızda bile varsayılan ayarlarla yalnızca text/html sıkıştırılır. Aşağıdaki blok /etc/nginx/conf.d/gzip.conf dosyasına konabilecek üretime hazır bir yapılandırmadır:
gzip on;
gzip_vary on; # Vary: Accept-Encoding başlığı ekler, ara önbellekler için şart
gzip_comp_level 5; # 1-9 arası; 5 iyi denge, 9 CPU'yu boşuna yakar
gzip_min_length 256; # 256 bayttan küçük yanıtı sıkıştırmak zarar
gzip_proxied any; # vekil arkasındaki isteklerde de sıkıştır
gzip_buffers 16 8k;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/javascript
application/json
application/xml
application/rss+xml
application/vnd.api+json
image/svg+xml
application/manifest+json;
Birkaç ayrıntı önemli. gzip_types listesinde text/html yazmanıza gerek yok, Nginx onu her zaman sıkıştırır; hatta listeye eklerseniz uyarı verir. gzip_comp_level için 9 kullanmak sanıldığının aksine iyi fikir değildir: 5'ten 9'a çıkarken dosya boyutu tipik olarak yalnızca %2-3 azalırken CPU maliyeti ikiye katlanır. Yüksek trafikte bu fark doğrudan yanıt süresine yansır. gzip_min_length ise küçük yanıtları korur; 100 baytlık bir JSON yanıtını sıkıştırdığınızda gzip başlık yükü yüzünden dosya büyüyebilir.
gzip_vary on satırını atlamak sinsi bir hatadır. Bu satır olmadan araya giren bir CDN ya da vekil, sıkıştırılmış bir yanıtı sıkıştırmayı desteklemeyen bir istemciye servis edebilir ve kullanıcı bozuk karakterler görür. Yapılandırmayı test edip yükleyin:
sudo nginx -t # söz dizimi kontrolü
sudo systemctl reload nginx # kesintisiz yeniden yükleme
Brotli: gzip'in Yerine mi, Yanına mı#
Brotli, Google'ın geliştirdiği ve tüm modern tarayıcıların desteklediği bir sıkıştırma algoritmasıdır. Metin içeriğinde gzip'e göre tipik olarak %10-20 daha küçük çıktı üretir; özellikle HTML ve CSS'te fark belirgindir çünkü Brotli web'de sık geçen kelimeleri içeren yerleşik bir sözlükle gelir. Nginx'te Brotli çekirdekte bulunmaz, modül olarak derlenmesi ya da paket olarak kurulması gerekir.
brotli on;
brotli_comp_level 5; # 0-11; 11 statik dosyalar için, 4-5 dinamik için
brotli_static on; # varsa hazır .br dosyasını servis et
brotli_types
text/plain
text/css
application/javascript
application/json
image/svg+xml;
Kritik nokta şudur: Brotli gzip'in yerine geçmez, yanına eklenir. Her ikisini birden açık tutun. Tarayıcı Accept-Encoding: gzip, deflate, br başlığıyla desteklediklerini bildirir, Nginx de en iyisini seçer. Brotli'yi tek başına bırakırsanız eski istemciler ve bazı bot/araçlar sıkıştırılmamış içerik alır.
Dinamik yanıtlarda brotli_comp_level değerini yüksek tutmayın; seviye 11 çok yavaştır ve her istekte yeniden hesaplanır. Yüksek seviyeyi yalnızca derleme sırasında üretilen statik dosyalar için kullanın; bu ayrımı brotli_static sağlar.
Önceden Sıkıştırılmış Dosya Servis Etmek#
En verimli yaklaşım, sıkıştırmayı her istekte yapmak yerine derleme aşamasında bir kez yapıp sonucu diske yazmaktır. app.js yanına app.js.gz ve app.js.br koyarsanız Nginx bunları CPU harcamadan doğrudan gönderir. Bu, yüksek trafikli sitelerde sunucu yükünde ölçülebilir bir düşüş sağlar.
gzip_static on; # ngx_http_gzip_static_module gerekir
brotli_static on; # brotli modülü gerekir
Derleme adımında dosyaları şöyle üretebilirsiniz:
# dist klasöründeki metin varlıklarını hem gzip hem brotli ile önceden sıkıştır
find dist -type f \( -name "*.js" -o -name "*.css" -o -name "*.svg" -o -name "*.json" \) \
-exec gzip -9 -k -f {} \; \
-exec brotli -q 11 -f {} \;
# Sonuç: app.js, app.js.gz, app.js.br yan yana durur
ls -lh dist/assets/ | head
Burada -k bayrağı orijinal dosyayı korur; unutursanız gzip kaynak dosyayı siler ve sıkıştırmayı desteklemeyen istemciler 404 alır. Sıkıştırılmış dosyaların tarihinin orijinalden yeni olduğundan emin olun, aksi hâlde gzip_static eski sürümü servis edebilir.
Statik Dosyalar için Önbellek Başlıkları#
Sıkıştırma transferi küçültür, önbellek ise transferi tamamen ortadan kaldırır. İkinci ziyarette tarayıcı dosyayı sunucudan hiç istemez. Bunu sağlayan Cache-Control başlığıdır. Nginx'te iki yol vardır: expires direktifi (kısayol) ve doğrudan add_header Cache-Control (tam kontrol).
# 1) İçerik hash'i taşıyan build çıktıları — bir yıl, asla yeniden doğrulama
location ~* "\.[0-9a-f]{8,}\.(js|css)$" {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
}
# 2) Sabit adlı statik varlıklar — uzun ama immutable değil
location ~* \.(png|jpe?g|gif|webp|avif|svg|ico|woff2?)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
access_log off;
}
# 3) HTML — asla uzun süre saklanmasın, her zaman doğrulansın
location ~* \.html$ {
add_header Cache-Control "no-cache, must-revalidate";
}
Üç kural üç farklı yaşam döngüsüne karşılık gelir ve karıştırılmaları pahalıya patlar. immutable işareti tarayıcıya "bu dosya asla değişmeyecek, kullanıcı sayfayı yenilese bile bana sorma" der. Bunu yalnızca adında içerik hash'i olan dosyalara verin. Sabit adlı style.css dosyasına immutable koyarsanız güncellemeniz ziyaretçilere bir yıl boyunca ulaşmaz ve normal yenileme bunu düzeltmez.
HTML'e uzun max-age vermek de klasik bir felakettir: fiyat değişikliğiniz, kampanya güncellemeniz ya da acil düzeltmeniz kullanıcının tarayıcısında saatlerce takılı kalır. HTML her zaman kısa ömürlü olmalı, uzun ömrü statik varlıklar taşımalıdır. Apache tarafında aynı mantığın karşılığı için .htaccess önbellek ve sıkıştırma rehberine bakabilirsiniz.
ETag, Last-Modified ve Koşullu İstekler#
Önbellek süresi dolduğunda tarayıcı dosyayı baştan indirmez; önce "bu dosya değişti mi" diye sorar. Bu koşullu isteği iki başlık yürütür. Last-Modified dosyanın değişiklik tarihini, ETag ise içeriğin parmak izini taşır. Dosya değişmemişse sunucu gövdesiz bir 304 Not Modified döner ve bant genişliği harcanmaz.
Nginx'te her ikisi de varsayılan olarak açıktır. ETag'i kapatmanın tek makul sebebi, aynı içeriği birden fazla sunucudan servis ediyor olmanızdır: Nginx ETag'i dosyanın inode değil, mtime ve boyutundan üretir, bu yüzden çoklu sunucuda genellikle uyumlu kalır ama dağıtım süreciniz dosya tarihlerini değiştiriyorsa ETag'ler sunucular arası tutmaz ve gereksiz yere tam indirme yapılır.
etag on; # varsayılan; kapatmak için off
if_modified_since exact; # tarih karşılaştırmasını tam eşleşmeli yap
Sonucu doğrulamak en önemli adımdır. curl ile hem sıkıştırmayı hem önbellek başlıklarını tek seferde görebilirsiniz:
# Sıkıştırma çalışıyor mu (Content-Encoding satırına bakın)
curl -sI -H "Accept-Encoding: gzip, br" https://firmaniz.com/assets/app.css \
| grep -Ei "content-encoding|content-length|cache-control|vary|etag"
# Örnek beklenen çıktı:
# content-encoding: br
# cache-control: public, max-age=31536000, immutable
# vary: Accept-Encoding
# etag: "68a1c3f2-5e40"
# 304 dönüyor mu (ETag ile koşullu istek)
curl -sI -H 'If-None-Match: "68a1c3f2-5e40"' https://firmaniz.com/assets/app.css | head -1
# HTTP/2 304
Bu üç komut, yapılandırmanızın gerçekten çalıştığının kanıtıdır. Tarayıcı geliştirici araçlarındaki Network sekmesi de aynı bilgiyi verir ama curl ara katmanların (CDN, vekil) etkisini daha net gösterir.
Sık Yapılan Hatalar ve Tuzaklar#
add_header'ın miras kuralını bilmemek. Nginx'te add_header direktifleri miras alınmaz: bir location bloğu kendi add_header satırını tanımlarsa üst bloktaki tüm add_header satırları o location için kaybolur. Yani server düzeyinde güvenlik başlıkları tanımladıysanız ve bir location içinde Cache-Control eklediyseniz, o location güvenlik başlıklarını göndermez. Çözüm, gerekli başlıkları o blokta tekrar yazmak ya da Nginx 1.7.5+ ile gelen always bayrağını bilinçli kullanmaktır.
Sıkıştırılmış içeriği tekrar sıkıştırmak. gzip_types listesine image/png, video/mp4 ya da font/woff2 eklemek CPU harcar ve hiçbir kazanç sağlamaz. Liste yalnızca metin tabanlı tiplerden oluşmalıdır.
Vary başlığını unutmak. gzip_vary on yoksa arada duran önbellek katmanları yanlış kodlanmış içeriği yanlış istemciye verebilir. Bu hata genellikle CDN devreye alındıktan sonra ortaya çıkar ve teşhisi zordur.
HTML'i uzun süre önbelleklemek. Sitenin "güncellenmediği" şikâyetlerinin en yaygın sebebi budur. HTML yanıtına max-age=86400 koyarsanız yaptığınız her değişiklik ziyaretçide bir gün gecikir.
Dinamik PHP çıktısında Brotli seviyesini yükseltmek. brotli_comp_level 11 her istekte yeniden hesaplandığı için CPU'yu kilitler ve yanıt süresini uzatır. Yüksek seviye yalnızca brotli_static ile önceden üretilmiş dosyalar içindir.
Sıkıştırmayı ölçmeden "tamam" demek. nginx -t sadece söz dizimini doğrular, sıkıştırmanın çalıştığını göstermez. Yukarıdaki curl komutunu çalıştırıp content-encoding satırını gözünüzle görmeden bitmiş saymayın. Sayfa hızının tarayıcı tarafındaki karşılığını görmek isterseniz GTmetrix ile hız testi yazısındaki ölçüm yöntemi işinizi görür.
Sıkça Sorulan Sorular#
gzip mi brotli mi kullanmalıyım#
İkisini birden kullanın. Brotli metin içerikte gzip'ten tipik olarak %10-20 daha iyi sıkıştırır ama tüm istemciler desteklemez. Her ikisi açıkken tarayıcı Accept-Encoding başlığıyla desteklediklerini bildirir, Nginx en iyisini seçer. Brotli'yi tek başına bırakmak eski istemcilere ve bazı botlara sıkıştırılmamış içerik gönderilmesine yol açar.
gzip_comp_level kaç olmalı#
Dinamik içerik için 4-6 arası ideal dengeyi verir; genellikle 5 önerilir. 9'a çıkmak dosya boyutunu yalnızca birkaç yüzde küçültürken CPU maliyetini belirgin biçimde artırır ve yoğun trafikte yanıt süresini uzatır. Yüksek seviyeleri yalnızca önceden sıkıştırıp diske yazdığınız statik dosyalarda kullanın.
Cache-Control ile expires arasındaki fark nedir#
expires Nginx'e özgü bir kısayoldur; hem Expires hem de Cache-Control: max-age başlığını otomatik üretir. add_header Cache-Control ise public, immutable, stale-while-revalidate gibi direktifleri tam kontrol etmenizi sağlar. Basit senaryolarda expires yeterlidir; immutable gibi ek direktifler gerektiğinde add_header kullanın.
Statik dosya önbelleğini ne kadar süre ayarlamalıyım#
Dosya adında içerik hash'i varsa bir yıl (max-age=31536000, immutable) verin, çünkü içerik değiştiğinde dosya adı da değişir ve önbellek doğal olarak geçersiz olur. Sabit adlı görseller için 30 gün makul bir aralıktır. HTML için asla uzun süre vermeyin; no-cache, must-revalidate doğru seçimdir.
Nginx sıkıştırma ayarını değiştirdim, nasıl kontrol ederim#
Terminalden curl -sI -H "Accept-Encoding: gzip, br" https://firmaniz.com/assets/app.css komutunu çalıştırıp content-encoding satırına bakın. br ya da gzip görüyorsanız çalışıyordur; başlık hiç yoksa sıkıştırma devreye girmemiştir. Yanıtın 256 bayttan küçük olmadığından ve MIME tipinin gzip_types listesinde bulunduğundan emin olun.
Paylaşımlı hostingte bu ayarları yapabilir miyim#
Nginx yapılandırma dosyalarına erişim genellikle root gerektirir, bu yüzden paylaşımlı pakette doğrudan düzenleyemezsiniz. Ancak çoğu paylaşımlı sunucuda gzip zaten sağlayıcı tarafından açıktır ve Apache/LiteSpeed kullanılıyorsa .htaccess üzerinden sıkıştırma ve önbellek başlıklarını kendiniz tanımlayabilirsiniz. Tam kontrol istiyorsanız VDS ya da sanal sunucuya geçmeniz gerekir.
Kapanış#
Nginx'te sıkıştırma ve statik önbellek, doğru kurulduğunda uygulama kodunuza dokunmadan sayfa ağırlığını yarıya, tekrar ziyaret maliyetini sıfıra indirir. Aklınızda tutmanız gereken dört alışkanlık şunlar: yalnızca metin tiplerini sıkıştırın ve gzip_vary on satırını asla atlamayın, dinamik içerikte sıkıştırma seviyesini orta tutup yüksek seviyeyi önceden üretilmiş dosyalara saklayın, immutable işaretini sadece hash'li dosya adlarına verin ve HTML'i kısa ömürlü tutun, son olarak her değişikliği curl -I ile doğrulayın.
Bu ayarları kendi sunucunuzda serbestçe yapabilmek için root erişimli bir ortama ihtiyacınız var; VDS ve sanal sunucu paketlerinde Nginx yapılandırmasını uçtan uca kontrol edebilirsiniz. Kurulumu ve ince ayarı bize bırakmak isterseniz sunucu yönetimi hizmetimiz sıkıştırma, önbellek ve HTTP/2 ayarlarını trafiğinize göre düzenler. Statik varlıkları coğrafi olarak dağıtmak isterseniz web hosting paketlerimizle birlikte gelen altyapı çoğu proje için yeterli bir başlangıç sunar.