Caddy ile Node.js ve Next.js Uygulaması Sunumu

Modern web geliştirme dünyasında Node.js ve Next.js uygulamalarını production ortamında sunmak, doğru araçları seçmekle başlar. Nginx veya Apache gibi köklü web sunucuları hala popülerliğini korusa da Caddy, otomatik HTTPS desteği ve sade yapılandırma sözdizimi ile sistem yöneticilerinin radarına hızla girdi. Bu yazıda gerçek dünya senaryoları üzerinden Caddy ile Node.js ve Next.js uygulamalarını nasıl sunacağınızı adım adım ele alacağız.

Caddy Neden Bu İş İçin Uygun?

Caddy’yi rakiplerinden ayıran en önemli özellik, Let’s Encrypt üzerinden otomatik SSL sertifikası alıp yenilemesi. Nginx’te bu iş için certbot kurup cron job tanımlamanız gerekirken Caddy bunu kutudan çıkar çıkmaz hallediyor. Node.js uygulamaları tipik olarak belirli bir port üzerinde çalışır ve bu uygulamaları dış dünyaya açmak için bir reverse proxy katmanına ihtiyaç duyarsınız. Caddy bu katmanı hem çok daha kolay yapılandırma imkânı sunarak hem de HTTP/2, HTTP/3 desteğiyle birlikte geliyor.

Next.js özelinde konuşursak, uygulamanın static asset’leri, API route’ları ve SSR sayfalarını aynı anda yönetmek gerekiyor. Caddy bu karmaşık senaryoyu temiz bir Caddyfile ile kolayca ele alıyor.

Caddy Kurulumu

Ubuntu/Debian Üzerinde Kurulum

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

Kurulum tamamlandıktan sonra Caddy otomatik olarak systemd servisi olarak kayıt olur. Servisi başlatıp durumunu kontrol edelim:

sudo systemctl enable caddy
sudo systemctl start caddy
sudo systemctl status caddy

CentOS/RHEL/Rocky Linux Üzerinde Kurulum

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

Kurulum sonrası Caddy binary’sinin nerede olduğunu ve versiyonu doğrulayalım:

which caddy
caddy version

Temel Caddyfile Yapısı

Caddy’nin yapılandırma dosyası /etc/caddy/Caddyfile konumunda bulunur. Nginx’in direktif yükü ile karşılaştırıldığında Caddyfile’ın ne kadar sade olduğunu anlamak için basit bir örnekle başlayalım.

myapp.example.com {
    reverse_proxy localhost:3000
}

Evet, bu kadar. Bu üç satır Caddy’ye şunu söylüyor: myapp.example.com alan adına gelen istekleri localhost:3000 adresine ilet ve bunun için otomatik olarak SSL sertifikası al. Nginx’te aynı işlev için sunucu bloğu, SSL ayarları, proxy header’ları ve certbot konfigürasyonu dahil onlarca satır yazmanız gerekirdi.

Node.js Uygulaması Reverse Proxy Yapılandırması

Diyelim ki basit bir Express.js API’nız var ve 3000 portunda çalışıyor. Production ortamında bu uygulamayı pm2 ile yönettiğinizi varsayalım.

Önce pm2 kurulumu ve uygulamayı başlatma:

npm install -g pm2
cd /var/www/myapi
pm2 start app.js --name "my-api"
pm2 startup
pm2 save

Şimdi Caddyfile’ı düzenleyelim:

api.example.com {
    reverse_proxy localhost:3000 {
        header_up Host {host}
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-For {remote_host}
        header_up X-Forwarded-Proto {scheme}
    }

    log {
        output file /var/log/caddy/api-access.log {
            roll_size 10mb
            roll_keep 10
        }
        format json
    }
}

header_up direktifleri, uygulamanızın gerçek client IP adresini ve kullanılan protokolü görebilmesi için önemli. Express uygulamanızda trust proxy ayarını aktif etmeyi unutmayın:

const express = require('express');
const app = express();
app.set('trust proxy', 1);

Yapılandırma değişikliklerini uygulamak için Caddy’ye reload sinyali gönderelim:

sudo systemctl reload caddy
# veya
caddy reload --config /etc/caddy/Caddyfile

Next.js Uygulaması Yapılandırması

Next.js biraz daha fazla dikkat gerektiriyor çünkü hem frontend hem de API route’larını yönetiyor, aynı zamanda static dosyaları ve özel başlıkları da düşünmek gerekiyor.

Standalone Modda Next.js

Production ortamında Next.js uygulamasını standalone modda derlemek performans ve taşınabilirlik açısından en iyi pratiktir. next.config.js dosyasına şunu ekleyin:

/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'standalone',
  poweredByHeader: false,
};

module.exports = nextConfig;

Build alıp uygulamayı başlatın:

cd /var/www/nextapp
npm run build
pm2 start npm --name "nextjs-app" -- start
# veya standalone için
pm2 start node --name "nextjs-app" -- .next/standalone/server.js
pm2 save

Next.js için Caddyfile Yapılandırması

Next.js uygulamaları için özel başlıklar, static dosya cache’leme ve güvenlik header’ları eklenmiş kapsamlı bir yapılandırma:

nextapp.example.com {
    # Static dosyalar için önbellekleme
    @static {
        path /_next/static/*
        path /favicon.ico
        path /robots.txt
        path /sitemap.xml
    }
    handle @static {
        header Cache-Control "public, max-age=31536000, immutable"
        reverse_proxy localhost:3000
    }

    # Public dizinindeki resimler için
    @images {
        path /images/*
        path /*.png
        path /*.jpg
        path /*.webp
        path /*.svg
    }
    handle @images {
        header Cache-Control "public, max-age=86400"
        reverse_proxy localhost:3000
    }

    # Güvenlik başlıkları
    header {
        X-Frame-Options "SAMEORIGIN"
        X-Content-Type-Options "nosniff"
        X-XSS-Protection "1; mode=block"
        Referrer-Policy "strict-origin-when-cross-origin"
        Permissions-Policy "camera=(), microphone=(), geolocation=()"
        -Server
    }

    # Ana reverse proxy
    reverse_proxy localhost:3000 {
        header_up Host {host}
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-For {remote_host}
        header_up X-Forwarded-Proto {scheme}
    }

    log {
        output file /var/log/caddy/nextapp-access.log {
            roll_size 20mb
            roll_keep 15
        }
        format json
    }
}

Bu yapılandırmada dikkat çekilmesi gereken noktalar şunlar:

  • @static matcher: /_next/static/ altındaki dosyalar immutable cache header’ı alıyor. Next.js bu dosyaların adına hash ekleyerek content-addressable hale getirdiği için bir yıllık cache tamamen güvenli.
  • -Server header direktifi: Caddy’nin server bilgisini response header’larında göndermesini engeller, hafif bir güvenlik önlemi.
  • Permissions-Policy: Modern tarayıcılarda kamera, mikrofon gibi özelliklerin izinlerini kontrol ediyor.

Birden Fazla Uygulama Yönetimi

Gerçek dünyada tek bir sunucuda birden fazla uygulama çalıştırmanız oldukça yaygın. Caddy bu senaryoyu zarif bir şekilde ele alıyor. Her alan adı veya subdomain kendi bloğuna sahip olabilir:

# E-ticaret ana uygulaması
shop.example.com {
    reverse_proxy localhost:3000
    header -Server
}

# Yönetim paneli
admin.example.com {
    reverse_proxy localhost:3001
    
    # IP bazlı erişim kısıtlaması
    @blocked not remote_ip 192.168.1.0/24 10.0.0.0/8
    respond @blocked "Erişim reddedildi" 403
}

# API servisleri
api.example.com {
    reverse_proxy localhost:3002
    
    # Rate limiting
    rate_limit {remote_host} 100r/m
}

# Blog (ayrı Next.js instance)
blog.example.com {
    reverse_proxy localhost:3003
}

Subdirectory ile Uygulama Sunumu

Bazen farklı uygulamaları aynı domain altında farklı path’lerde sunmak gerekebilir. Örneğin / ana Next.js uygulaması, /api/v2 ise ayrı bir Node.js mikro servisi olabilir:

example.com {
    # API v2 - ayrı Node.js servisi
    handle /api/v2/* {
        uri strip_prefix /api/v2
        reverse_proxy localhost:4000
    }

    # Statik dokümantasyon
    handle /docs/* {
        root * /var/www/docs
        file_server
    }

    # Her şey else Next.js'e
    handle {
        reverse_proxy localhost:3000
    }

    header -Server
}

Burada uri strip_prefix direktifi kritik bir rol oynuyor. /api/v2/users gibi bir istek Node.js servisine /users olarak iletiliyor, bu sayede servisin kendi yapılandırmasında prefix’i bilmesine gerek kalmıyor.

WebSocket Desteği

Next.js uygulamalarında hot reload veya gerçek zamanlı özellikler için WebSocket desteği gerekebilir. Caddy bunu otomatik olarak algılayıp yönetiyor ama production’da Socket.io veya benzeri bir kütüphane kullanıyorsanız açıkça belirtmek iyi bir pratik:

realtime.example.com {
    reverse_proxy localhost:3000 {
        header_up Host {host}
        header_up X-Real-IP {remote_host}
        
        # WebSocket için transport upgrade
        transport http {
            versions h2c
        }
    }
}

Caddy, Upgrade: websocket header’ını gördüğünde otomatik olarak WebSocket bağlantısına geçiş yapar. Bu konuda Nginx’ten çok daha az elle müdahale gerekiyor.

HTTPS ve Sertifika Yönetimi

Caddy’nin belki de en güçlü yanı olan otomatik HTTPS yönetimini biraz daha derinlemesine ele alalım.

Özel Sertifika Kullanımı

Kurumsal ortamlarda kendi CA’nızdan aldığınız sertifikaları kullanmak isteyebilirsiniz:

intranet.company.local {
    tls /etc/ssl/certs/company.crt /etc/ssl/private/company.key
    reverse_proxy localhost:3000
}

Wildcard Sertifika

Birden fazla subdomain için wildcard sertifika almanız gerekiyorsa Caddy bunu DNS-01 challenge ile yapabiliyor. Cloudflare kullandığınızı varsayalım:

# Cloudflare DNS plugin'ini yükle
caddy add-package github.com/caddy-dns/cloudflare
{
    acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}

*.example.com {
    tls {
        dns cloudflare {env.CLOUDFLARE_API_TOKEN}
    }
    
    @nextapp host app.example.com
    handle @nextapp {
        reverse_proxy localhost:3000
    }
    
    @api host api.example.com
    handle @api {
        reverse_proxy localhost:4000
    }
}

Ortam değişkenini systemd servis dosyasına ekleyin:

sudo systemctl edit caddy

Açılan editörde:

[Service]
Environment=CLOUDFLARE_API_TOKEN=your_api_token_here

Performans Optimizasyonları

Sıkıştırma

Caddy varsayılan olarak gzip ve zstd sıkıştırmayı destekler:

example.com {
    encode {
        zstd
        gzip 6
        minimum_length 1024
    }
    reverse_proxy localhost:3000
}

minimum_length 1024 direktifi 1KB’dan küçük yanıtları sıkıştırmamak için kullanılıyor, küçük yanıtlarda sıkıştırmanın overhead’i kazançtan büyük olabiliyor.

Buffer ve Timeout Ayarları

Büyük dosya upload’ları veya uzun süren API istekleri için timeout değerlerini ayarlamak önemli:

upload.example.com {
    reverse_proxy localhost:3000 {
        flush_interval -1
        
        transport http {
            dial_timeout 10s
            response_header_timeout 30s
            read_timeout 300s
            write_timeout 300s
        }
    }
    
    request_body {
        max_size 100MB
    }
}

flush_interval -1 streaming yanıtlar için gerekli, özellikle Next.js’in Streaming SSR özelliğini kullanıyorsanız bu ayar kritik önem taşıyor.

Monitoring ve Log Yönetimi

JSON formatında tuttuğunuz Caddy loglarını işlemek için basit bir script işinizi görebilir:

#!/bin/bash
# /usr/local/bin/caddy-log-report.sh

LOG_FILE="/var/log/caddy/nextapp-access.log"
DATE=$(date -d "yesterday" +%Y-%m-%d)

echo "=== Caddy Log Raporu: $DATE ==="
echo ""

echo "Toplam İstek Sayısı:"
grep "$DATE" "$LOG_FILE" | wc -l

echo ""
echo "HTTP Durum Kodları:"
grep "$DATE" "$LOG_FILE" | 
    python3 -c "
import sys, json, collections
codes = collections.Counter()
for line in sys.stdin:
    try:
        data = json.loads(line)
        codes[data.get('status', 'unknown')] += 1
    except:
        pass
for code, count in sorted(codes.items()):
    print(f'  {code}: {count}')
"

echo ""
echo "En Çok İstek Alan 10 URL:"
grep "$DATE" "$LOG_FILE" | 
    python3 -c "
import sys, json, collections
urls = collections.Counter()
for line in sys.stdin:
    try:
        data = json.loads(line)
        uri = data.get('request', {}).get('uri', '')
        if uri:
            urls[uri] += 1
    except:
        pass
for url, count in urls.most_common(10):
    print(f'  {count:6d}  {url}')
"

Bu scripti cron’a ekleyerek günlük rapor alabilirsiniz:

0 8 * * * /usr/local/bin/caddy-log-report.sh | mail -s "Günlük Caddy Raporu" [email protected]

Sık Karşılaşılan Sorunlar

Port 80/443 başka bir süreç tarafından kullanılıyorsa:

sudo ss -tlnp | grep -E ':80|:443'
sudo systemctl stop apache2  # veya nginx
sudo systemctl disable apache2
sudo systemctl restart caddy

Sertifika alınamıyorsa:

Caddy’nin sertifika alabilmesi için domain’in DNS kaydının sunucunuza işaret etmesi ve 80/443 portlarının dışarıdan erişilebilir olması şart. Caddy loglarını inceleyin:

sudo journalctl -u caddy -f
sudo tail -f /var/log/caddy/caddy.log

Next.js uygulaması “upstream unreachable” hatası veriyorsa:

pm2 ile uygulamanın gerçekten ayakta olduğunu doğrulayın:

pm2 list
pm2 logs nextjs-app --lines 50
curl -v http://localhost:3000

/etc/caddy/Caddyfile syntax hatası:

Reload öncesi her zaman syntax kontrolü yapın:

caddy validate --config /etc/caddy/Caddyfile

Güvenlik Duvarı Ayarları

UFW kullanıyorsanız gerekli portları açmayı unutmayın:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp  # HTTP/3 için QUIC protokolü UDP kullanır
sudo ufw reload
sudo ufw status

Sonuç

Caddy, Node.js ve Next.js uygulamalarını production ortamında sunmak için son derece pratik bir çözüm sunuyor. Otomatik HTTPS, sade yapılandırma sözdizimi ve modern protokol desteği (HTTP/2, HTTP/3) ile birlikte geliyor olması, özellikle orta ölçekli projelerde Nginx’e göre ciddi bir zaman tasarrufu sağlıyor.

Bu yazıda ele aldığımız senaryolar, gerçek dünya uygulamalarının büyük çoğunluğunu kapsıyor: tek uygulama reverse proxy’den birden fazla servise, WebSocket desteğinden wildcard sertifikalara kadar. Caddy’nin JSON tabanlı API’si sayesinde dinamik yapılandırma da mümkün, ileride bu konuyu ayrı bir yazıda ele alabiliriz.

Yeni bir proje başlatıyorsanız veya mevcut Nginx yapılandırmanızı basitleştirmek istiyorsanız Caddy’yi ciddi ciddi değerlendirin. Öğrenme eğrisi oldukça düz ve dokümantasyonu başarılı. Caddyfile’ınızı hazırladıktan sonra sertifika yenileme, HTTP’den HTTPS’e yönlendirme gibi rutin işleri bir daha düşünmek zorunda kalmayacaksınız.

Yorum yapın