Caddy ile Hugo ve Jekyll Statik Site Sunumu

Statik site üreteclerini production ortamına taşımak, dinamik CMS alternatiflerine kıyasla hem daha güvenli hem de çok daha az kaynak tüketir. Hugo veya Jekyll ile oluşturduğun siteyi Nginx veya Apache ile sunmak elbette mümkün, ancak Caddy bu işi inanılmaz derecede basitleştiriyor. Otomatik HTTPS, sıfır bağımlılık ve son derece okunabilir yapılandırma dosyası ile Caddy, statik site sunumu için neredeyse ideal bir araç haline geliyor. Bu yazıda hem Hugo hem Jekyll için gerçek dünya senaryolarını ele alacağız.

Caddy Neden Statik Siteler İçin İyi Bir Seçim

Nginx’i yıllarca kullandıktan sonra Caddy’ye geçtiğimde en çok dikkatimi çeken şey, SSL sertifikası için tek bir satır bile yazmak zorunda kalmamamdı. Let’s Encrypt entegrasyonu tamamen otomatik çalışıyor, sertifika yenileme arka planda halloluyor ve sen sadece sitenin içeriğine odaklanabiliyorsun.

Statik siteler için Caddy’nin öne çıkan avantajları şunlar:

  • Sıfır bağımlılık: Tek binary, tek config dosyası, çalışıyor
  • Otomatik TLS: Let’s Encrypt veya ZeroSSL ile otomatik sertifika
  • HTTP/2 ve HTTP/3 desteği: Statik dosyaları hızlı sunmak için optimize edilmiş
  • Gzip/Zstd sıkıştırma: Tek direktifle aktif edilebiliyor
  • Basit redirect yönetimi: Hugo ve Jekyll’in URL yapısına uyum sağlamak kolay

Caddy Kurulumu

Debian/Ubuntu tabanlı sistemlerde resmi repoy ekleyerek kurulum yapalım:

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl

curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | 
  sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg

curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | 
  sudo tee /etc/apt/sources.list.d/caddy-stable.list

sudo apt update && sudo apt install caddy

RHEL/CentOS/Rocky Linux için:

dnf install 'dnf-command(copr)'
dnf copr enable @caddy/caddy
dnf install caddy

Kurulumdan sonra servis durumunu kontrol et:

sudo systemctl enable --now caddy
sudo systemctl status caddy
caddy version

Caddy varsayılan olarak /etc/caddy/Caddyfile dosyasını kullanıyor. Bu dosya Nginx’in nginx.conf karşılığı ama çok daha okunabilir.

Hugo Sitesi Hazırlama

Önce Hugo’yu kuralım. Statik site üreteclerinin en hızlısı olan Hugo, Go ile yazılmış ve build süreleri milisaniyeler mertebesinde.

# Ubuntu/Debian için snap ile
sudo snap install hugo

# Ya da binary olarak
wget https://github.com/gohugoio/hugo/releases/download/v0.120.0/hugo_extended_0.120.0_linux-amd64.deb
sudo dpkg -i hugo_extended_0.120.0_linux-amd64.deb

# Versiyon kontrolü
hugo version

Yeni bir Hugo sitesi oluşturup build edelim:

hugo new site myblog
cd myblog

# Tema ekleyelim (örnek olarak PaperMod)
git init
git submodule add https://github.com/adityatelange/hugo-PaperMod themes/PaperMod

# hugo.toml düzenle
cat >> hugo.toml << 'EOF'
theme = "PaperMod"
baseURL = "https://myblog.example.com"
languageCode = "tr-tr"
title = "Benim Blogum"
EOF

# Test içerik ekle
hugo new content posts/ilk-yazi.md

# Site build et
hugo --minify

# Build çıktısı public/ dizinine gider
ls public/

hugo --minify komutu HTML, CSS ve JS dosyalarını minify ederek boyutu küçülttür. Production build için bunu kullanman önerilir.

Caddy ile Hugo Sitesi Sunumu

Şimdi Caddy’yi Hugo çıktısını sunacak şekilde yapılandıralım. /etc/caddy/Caddyfile dosyasını düzenleyelim:

myblog.example.com {
    root * /var/www/myblog/public
    file_server

    # Gzip ve Zstd sıkıştırma
    encode zstd gzip

    # 404 sayfası için Hugo'nun custom 404'ü
    handle_errors {
        rewrite * /{err.status_code}.html
        file_server
    }

    # Güvenlik başlıkları
    header {
        X-Content-Type-Options nosniff
        X-Frame-Options DENY
        Referrer-Policy strict-origin-when-cross-origin
        -Server
    }

    # Cache headers - statik dosyalar için
    @static {
        path *.css *.js *.png *.jpg *.jpeg *.gif *.ico *.svg *.woff *.woff2
    }
    header @static Cache-Control "public, max-age=31536000, immutable"
}

Site dosyalarını doğru konuma taşıyalım:

sudo mkdir -p /var/www/myblog
sudo cp -r /home/user/myblog/public /var/www/myblog/
sudo chown -R caddy:caddy /var/www/myblog

# Caddy config doğrula
sudo caddy validate --config /etc/caddy/Caddyfile

# Caddy'yi yeniden yükle
sudo systemctl reload caddy

Bu kadar. Caddy otomatik olarak Let’s Encrypt’ten SSL sertifikası alacak ve siteniz HTTPS üzerinden yayına girecek.

Jekyll Sitesi Hazırlama

Jekyll, Ruby ekosistemiyle çalışıyor ve GitHub Pages’in resmi statik site üreteci. Kurulumu biraz daha uzun ama son derece kararlı bir araç.

# Ruby ve bağımlılıkları kur
sudo apt install ruby-full build-essential zlib1g-dev

# Gem PATH ayarı (.bashrc veya .zshrc'ye ekle)
echo 'export GEM_HOME="$HOME/gems"' >> ~/.bashrc
echo 'export PATH="$HOME/gems/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

# Jekyll ve Bundler kur
gem install jekyll bundler

# Yeni site oluştur
jekyll new mysite
cd mysite

# Bağımlılıkları yükle
bundle install

# Site build et
bundle exec jekyll build

# Çıktı _site/ dizinine gider
ls _site/

Jekyll yapılandırması için _config.yml dosyası:

cat > _config.yml << 'EOF'
title: "Sysadmin Notlarım"
description: "Linux ve Windows sistem yönetimi üzerine pratik notlar"
url: "https://sysadmin.example.com"
baseurl: ""

# Build ayarları
markdown: kramdown
highlighter: rouge
permalink: /:year/:month/:day/:title/

# Exclude listesi
exclude:
  - Gemfile
  - Gemfile.lock
  - node_modules
  - vendor/
EOF

# Production build
JEKYLL_ENV=production bundle exec jekyll build --destination ./_site

JEKYLL_ENV=production değişkeni ile build aldığında Jekyll bazı eklentiler production modunda çalışır ve analytics kodları gibi şeyler aktif olur.

Caddy ile Jekyll Sitesi Sunumu

Jekyll için Caddy yapılandırması Hugo’ya benzer ancak Jekyll’in varsayılan permalink yapısı için küçük bir fark var:

sysadmin.example.com {
    root * /var/www/mysite/_site
    file_server

    encode zstd gzip

    # Jekyll'in tarih tabanlı URL yapısı için trailing slash
    @trailing_slash {
        not path */
        not path *.* 
        file {path}/index.html
    }
    redir @trailing_slash {path}/ permanent

    # 404
    handle_errors {
        rewrite * /404.html
        file_server
    }

    header {
        Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
        X-Content-Type-Options nosniff
        X-Frame-Options SAMEORIGIN
        -Server
    }

    # Statik asset caching
    @assets {
        path /assets/* /images/*
    }
    header @assets Cache-Control "public, max-age=2592000"

    log {
        output file /var/log/caddy/sysadmin.log {
            roll_size 10mb
            roll_keep 5
        }
        format json
    }
}

Caddy’yi yeniden yüklemeden önce log dizinini oluştur:

sudo mkdir -p /var/log/caddy
sudo chown caddy:caddy /var/log/caddy
sudo systemctl reload caddy

Otomatik Deployment Pipeline

Manuel olarak dosya kopyalamak üretim ortamında kabul edilemez. Git push ile otomatik deploy için basit bir webhook mekanizması kuralım. Bu senaryo özellikle VPS’inde kendi CI/CD’sini kurmak isteyenler için çok işlevsel.

# Deploy script oluştur
sudo mkdir -p /opt/deploy
sudo tee /opt/deploy/hugo-deploy.sh << 'EOF'
#!/bin/bash

set -e

REPO_DIR="/opt/repos/myblog"
DEPLOY_DIR="/var/www/myblog/public"
LOG_FILE="/var/log/deploy/hugo-deploy.log"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')

echo "[$TIMESTAMP] Deploy başladı" >> "$LOG_FILE"

# Repoyu güncelle
cd "$REPO_DIR"
git pull origin main >> "$LOG_FILE" 2>&1

# Hugo build
hugo --minify --destination /tmp/hugo-build >> "$LOG_FILE" 2>&1

# Atomic swap - downtime olmadan
rsync -av --delete /tmp/hugo-build/ "$DEPLOY_DIR/" >> "$LOG_FILE" 2>&1

# Temizlik
rm -rf /tmp/hugo-build

echo "[$TIMESTAMP] Deploy tamamlandı" >> "$LOG_FILE"
EOF

sudo chmod +x /opt/deploy/hugo-deploy.sh
sudo mkdir -p /var/log/deploy
sudo chown -R caddy:caddy /var/log/deploy

rsync --delete ile atomic swap yapıyoruz. Bu sayede eski dosyalar kaldırılırken siteniz bir an bile çevrimdışı kalmıyor.

Jekyll için ayrı bir deploy script:

sudo tee /opt/deploy/jekyll-deploy.sh << 'EOF'
#!/bin/bash

set -e

REPO_DIR="/opt/repos/mysite"
DEPLOY_DIR="/var/www/mysite"
LOG_FILE="/var/log/deploy/jekyll-deploy.log"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')

echo "[$TIMESTAMP] Jekyll deploy başladı" >> "$LOG_FILE"

cd "$REPO_DIR"
git pull origin main >> "$LOG_FILE" 2>&1

# Bundle install (Gemfile.lock varsa hızlı)
bundle install >> "$LOG_FILE" 2>&1

# Production build
JEKYLL_ENV=production bundle exec jekyll build 
  --destination /tmp/jekyll-build >> "$LOG_FILE" 2>&1

rsync -av --delete /tmp/jekyll-build/ "$DEPLOY_DIR/_site/" >> "$LOG_FILE" 2>&1
rm -rf /tmp/jekyll-build

echo "[$TIMESTAMP] Jekyll deploy tamamlandı" >> "$LOG_FILE"
EOF

sudo chmod +x /opt/deploy/jekyll-deploy.sh

Birden Fazla Siteyi Tek Caddy ile Yönetmek

Gerçek dünyada genellikle tek sunucuda birden fazla statik site çalıştırman gerekiyor. Caddy bunu Caddyfile içinde blokları ayırarak çözüyor:

# Hugo sitesi
myblog.example.com {
    root * /var/www/myblog/public
    file_server
    encode zstd gzip

    header {
        X-Frame-Options DENY
        -Server
    }
}

# Jekyll sitesi
docs.example.com {
    root * /var/www/docs/_site
    file_server
    encode zstd gzip

    # Docs sitesi için arama indexi hariç her şeyi cache'le
    @no_cache {
        path /search.json /sitemap.xml
    }
    header @no_cache Cache-Control "no-cache"

    @static {
        not path @no_cache
    }
    header @static Cache-Control "public, max-age=3600"
}

# Wildcard ile tüm alt domainleri tek yerde yönet
# Bunun için DNS'te wildcard A kaydı gerekli
portfolio.example.com {
    root * /var/www/portfolio/public
    file_server
    encode gzip

    # HTTP'den HTTPS'e zaten otomatik yönlendirme yapıyor
    # www olmayan versiyona yönlendir
    @www host www.portfolio.example.com
    redir @www https://portfolio.example.com{uri} permanent
}

Birden fazla domain olduğunda Caddy her domain için ayrı sertifika alıyor ve yönetiyor. Yenileme işlemleri de tamamen otomatik.

Development ve Staging Ortamları

Production’a çıkmadan önce local veya staging ortamında test etmek şart. Caddy’nin local development için çok kullanışlı bir özelliği var: otomatik self-signed sertifika.

# Local geliştirme için Caddyfile (sudo gerektirmez)
cat > ~/Caddyfile.dev << 'EOF'
localhost:8080 {
    root * ./public
    file_server
    encode gzip
    
    # Değişiklikler için auto-refresh (Hugo'nun --watch ile birlikte kullan)
}
EOF

# Hugo'yu watch modunda başlat
hugo serve --watch &

# Veya production build'i Caddy ile sun
hugo --minify
caddy run --config ~/Caddyfile.dev

Staging ortamı için ayrı bir yapılandırma dosyası kullanmak iyi bir pratik:

sudo tee /etc/caddy/Caddyfile.staging << 'EOF'
staging.example.com {
    root * /var/www/staging/public
    file_server
    encode zstd gzip

    # Staging'e sadece ofis IP'sinden erişim
    @blocked not remote_ip 192.168.1.0/24 10.0.0.0/8
    respond @blocked 403

    # Basic auth ile ekstra koruma
    basicauth * {
        # caddy hash-password komutu ile üret
        deploy $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GTNmpkT/5qqR7hx4IjWJPDhjvG
    }
}
EOF

# Staging Caddy instance'ı başlat
sudo caddy run --config /etc/caddy/Caddyfile.staging &

caddy hash-password komutu ile şifre hash’i üretebilirsin:

caddy hash-password
# Şifreyi gir, hash'i alırsın

Hugo ve Jekyll İçin Caddy’de Özel Redirect Kuralları

Her iki statik site üretecinde de zaman zaman eski URL yapısından yeni yapıya geçiş yaparsın. Caddy bu redirect’leri çok temiz bir şekilde yönetiyor.

myblog.example.com {
    root * /var/www/myblog/public
    file_server
    encode zstd gzip

    # Eski /blog/ prefix'ini kaldır
    redir /blog/* /{path} permanent

    # Category sayfalarını yeni yapıya yönlendir  
    redir /category/* /categories/{path} permanent

    # Belirli eski yazıları yeni adreslerine yönlendir
    redir /2022/my-old-post /posts/my-old-post permanent
    redir /about-me /about permanent

    # Feed URL'lerini normalize et
    redir /feed /index.xml permanent
    redir /rss.xml /index.xml permanent
    redir /atom.xml /index.xml permanent
}

Performans İzleme ve Log Analizi

Caddy’nin JSON formatındaki logları, analiz için çok uygun:

# Son 100 isteği görüntüle
sudo tail -n 100 /var/log/caddy/sysadmin.log | jq .

# En çok istek alan sayfalar
sudo cat /var/log/caddy/sysadmin.log | 
  jq -r '.request.uri' | 
  sort | uniq -c | sort -rn | head 20

# 404 hataları
sudo cat /var/log/caddy/sysadmin.log | 
  jq -r 'select(.status == 404) | .request.uri' | 
  sort | uniq -c | sort -rn

# Ortalama yanıt süresi (ms cinsinden)
sudo cat /var/log/caddy/sysadmin.log | 
  jq -r '.duration' | 
  awk '{sum+=$1; count++} END {print "Ortalama:", sum/count*1000, "ms"}'

Bu log analizleri sayesinde hangi sayfaların 404 verdiğini görebilir, hangi varlıkların yavaş yüklendiğini tespit edebilirsin.

Güvenlik Sertleştirmesi

Statik siteler dinamik sitelere kıyasla çok daha güvenli olsa da bazı temel güvenlik önlemleri almak şart:

myblog.example.com {
    root * /var/www/myblog/public
    file_server

    # Gizli dosyalara erişimi engelle
    @hidden {
        path */.git/* */.env */.htaccess */Makefile
    }
    respond @hidden 404

    # Sadece izin verilen HTTP metodları
    @disallowed {
        not method GET HEAD OPTIONS
    }
    respond @disallowed 405

    # Content Security Policy
    header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:"
    header X-Content-Type-Options nosniff
    header X-Frame-Options DENY
    header Referrer-Policy strict-origin-when-cross-origin
    header Permissions-Policy "camera=(), microphone=(), geolocation=()"
    header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"

    # Server bilgisini gizle
    header -Server

    encode zstd gzip
}

.git dizininin web üzerinden erişilebilir olması, özellikle deployment scriptlerinde git pull kullanıyorsan kritik bir güvenlik açığı. Bu satır onu tamamen engelliyor.

Systemd ile Otomatik Deploy Servisi

Deploy scriptlerini cron veya webhook ile tetiklemek yerine systemd path unit kullanmak daha elegant bir çözüm:

sudo tee /etc/systemd/system/hugo-deploy.path << 'EOF'
[Unit]
Description=Hugo Deploy Trigger
After=network.target

[Path]
PathChanged=/opt/repos/myblog/.git/refs/heads/main
Unit=hugo-deploy.service

[Install]
WantedBy=multi-user.target
EOF

sudo tee /etc/systemd/system/hugo-deploy.service << 'EOF'
[Unit]
Description=Hugo Deploy Service
After=network.target

[Service]
Type=oneshot
ExecStart=/opt/deploy/hugo-deploy.sh
User=caddy
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl enable hugo-deploy.path
sudo systemctl start hugo-deploy.path

Bu yapılandırma ile git pull yaptığında veya CI/CD sisteminiz main branch’i güncellediğinde systemd otomatik olarak deploy scriptini tetikleyecek.

Sorun Giderme

Sık karşılaşılan durumlar ve çözümleri:

  • Caddy başlamıyor: sudo journalctl -xeu caddy ile detaylı log bak. Genellikle port 80/443 başka bir servis tarafından kullanılıyordur.
  • SSL sertifikası alınamıyor: Domain’in A kaydının sunucuya doğru gösterip göstermediğini kontrol et. Let’s Encrypt rate limit’e takılmış olabilirsin, caddy run --config /etc/caddy/Caddyfile ile foreground’da çalıştırıp hata mesajını gör.
  • 404 hataları çok fazla: Hugo veya Jekyll build’i tamamlanmadan Caddy’nin root dizini boş kalıyor. Deploy scriptinde build başarılı olana kadar eski versiyonu yayında bırakmak için rsync yerine symlink rotation kullanabilirsin.
  • Büyük resimler yavaş yükleniyor: Hugo’nun image processing özelliği ile resimleri build sırasında optimize et, WebP formatına dönüştür. Caddy tarafında yapabileceğin ek optimizasyon sınırlı.
  • Caddyfile sözdizimi hatası: sudo caddy validate --config /etc/caddy/Caddyfile komutu ile reload öncesi her zaman doğrula.

Sonuç

Caddy ile Hugo veya Jekyll kombinasyonu, statik site sunumu için gerçekten mükemmel bir üçlü oluşturuyor. Nginx ile aynı işi yapmak için onlarca satır SSL yapılandırması, mime type ayarları ve cache direktifi yazman gerekirken, Caddy ile bunların büyük çoğunluğu ya otomatik geliyor ya da tek satırda halloluyor.

Bu yazıda anlattığım kurulumun özeti şu şekilde:

  • Hugo/Jekyll: Statik site build et, çıktı bir dizine gidiyor
  • Caddy: O dizini root ve file_server ile sun, geri kalan her şey otomatik
  • Deploy pipeline: rsync --delete ile atomic swap, downtime yok
  • Güvenlik: Gizli dosyaları engelle, güvenlik başlıklarını ekle
  • İzleme: JSON loglar ile performans ve hata takibi

Eğer şu an Nginx veya Apache ile statik site sunuyorsan, Caddy’ye geçmek için gerçekten büyük bir neden var: karmaşıklığı azaltmak. Daha az karmaşıklık, daha az hata, daha az bakım yükü. Sysadmin olarak bundan daha değerli bir şey az bulunur.

Yorum yapın