Nginx ile Node.js Reverse Proxy Yapılandırması

Node.js uygulamanı yazdın, localhost:3000’de çalışıyor, şimdi ne yapacaksın? Doğrudan Node.js’i 80 portuna bağlamak hem güvensiz hem de pratik değil. İşte tam bu noktada Nginx devreye giriyor. Nginx’i bir reverse proxy olarak kullanmak, production ortamında Node.js uygulamalarını yönetmenin endüstri standardı haline gelmiş durumda. Bu yazıda sıfırdan başlayarak production-ready bir Nginx + Node.js kurulumu yapacağız.

Neden Reverse Proxy Kullanmalıyız?

Birçok geliştirici “neden ekstra bir katman ekleyeyim ki?” diye soruyor. Haklı bir soru, ama cevabı çok net:

  • SSL/TLS sonlandırma: Sertifika yönetimini Nginx üstlenir, Node.js uygulamanız bununla uğraşmaz
  • Statik dosya sunumu: CSS, JS, resim gibi statik dosyaları Nginx çok daha hızlı sunar
  • Yük dengeleme: Birden fazla Node.js instance’ı arasında trafiği dağıtabilirsin
  • Rate limiting: DDoS ve brute-force saldırılarına karşı koruma
  • Gzip sıkıştırma: Bant genişliği tasarrufu sağlar
  • Loglama ve monitoring: Merkezi bir yerden tüm trafiği izleyebilirsin
  • Zero-downtime deployment: Uygulamayı yeniden başlatırken kullanıcıları etkilemezsin

Node.js tek thread çalışır ve ağır statik dosya sunumunda zorlanır. Nginx ise bu iş için özel olarak tasarlanmış, event-driven mimarisiyle binlerce eşzamanlı bağlantıyı rahatlıkla idare eder.

Sistem Gereksinimleri ve Kurulum

Bu yazıda Ubuntu 22.04 LTS kullanacağız. CentOS veya Debian için komutlar küçük farklılıklar gösterebilir ama genel mantık aynı.

Önce sistemi güncelleyelim ve gerekli paketleri kuralım:

sudo apt update && sudo apt upgrade -y
sudo apt install -y curl git build-essential

Node.js Kurulumu

Node.js’i doğrudan apt ile kurmak yerine NodeSource repository kullanmak daha mantıklı. Bu sayede güncel LTS sürümünü alırsın:

curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
node --version
npm --version

Alternatif olarak nvm (Node Version Manager) kullanabilirsin. Birden fazla Node.js versiyonu yönetmen gerekiyorsa nvm çok daha pratik:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
source ~/.bashrc
nvm install 20
nvm use 20
nvm alias default 20

Nginx Kurulumu

sudo apt install -y nginx
sudo systemctl start nginx
sudo systemctl enable nginx
sudo systemctl status nginx

systemctl enable komutunu çalıştırmayı unutma. Sunucu yeniden başladığında Nginx otomatik olarak ayağa kalkmaz, aksi halde gece 3’te telefon alırsın.

Örnek Node.js Uygulaması Hazırlama

Gerçek bir senaryo üzerinden gidelim. Basit ama production’a taşınabilir bir Express uygulaması yazalım:

mkdir -p /var/www/myapp
cd /var/www/myapp
npm init -y
npm install express

Şimdi uygulama dosyasını oluşturalım:

cat > /var/www/myapp/app.js << 'EOF'
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

app.use(express.json());

// Sağlık kontrolü endpoint'i - monitoring için şart
app.get('/health', (req, res) => {
  res.json({
    status: 'ok',
    timestamp: new Date().toISOString(),
    uptime: process.uptime()
  });
});

app.get('/', (req, res) => {
  res.json({
    message: 'Merhaba! Node.js uygulaması çalışıyor.',
    environment: process.env.NODE_ENV || 'development'
  });
});

app.get('/api/users', (req, res) => {
  res.json([
    { id: 1, name: 'Ahmet Yılmaz' },
    { id: 2, name: 'Ayşe Kaya' }
  ]);
});

app.listen(PORT, '127.0.0.1', () => {
  console.log(`Uygulama ${PORT} portunda çalışıyor`);
});
EOF

Dikkat ettiysen listen fonksiyonunda 127.0.0.1 kullandım. Bu çok önemli bir güvenlik detayı: Node.js uygulamanın sadece localhost’tan erişilebilir olmasını sağlar. Dışarıdan doğrudan 3000 portuna erişimi engeller.

Uygulamayı test edelim:

cd /var/www/myapp
node app.js &
curl http://localhost:3000/health

Çalışıyorsa Ctrl+C ile durduralım ve PM2 kurulumuna geçelim.

PM2 ile Süreç Yönetimi

Node.js uygulamasını doğrudan node app.js ile çalıştırmak production için kabul edilemez. Uygulama çöktüğünde otomatik olarak yeniden başlamaz. PM2 bu sorunu çözer:

sudo npm install -g pm2
cd /var/www/myapp
pm2 start app.js --name "myapp"
pm2 startup systemd
# Çıktıda verilen komutu kopyalayıp çalıştır
sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u ubuntu --hp /home/ubuntu
pm2 save

PM2 durumunu kontrol etmek için:

pm2 status
pm2 logs myapp
pm2 monit

PM2’nin ecosystem dosyası ile çoklu instance başlatabilirsin. Bu özellikle çok çekirdekli sunucularda performans açısından kritik:

cat > /var/www/myapp/ecosystem.config.js << 'EOF'
module.exports = {
  apps: [{
    name: 'myapp',
    script: './app.js',
    instances: 'max',
    exec_mode: 'cluster',
    env: {
      NODE_ENV: 'production',
      PORT: 3000
    },
    error_file: '/var/log/pm2/myapp-error.log',
    out_file: '/var/log/pm2/myapp-out.log',
    log_file: '/var/log/pm2/myapp-combined.log',
    time: true
  }]
};
EOF

sudo mkdir -p /var/log/pm2
pm2 start ecosystem.config.js
pm2 save

instances: 'max' ayarı, sunucudaki CPU çekirdeği sayısı kadar Node.js process başlatır. 4 çekirdekli bir sunucuda 4 ayrı process çalışır ve Nginx bunlar arasında yük dengeler.

Nginx Reverse Proxy Yapılandırması

Asıl konumuza geldik. Nginx’in default yapılandırmasını düzenlemek yerine ayrı bir site yapılandırması oluşturmak çok daha temiz bir yaklaşım:

sudo nano /etc/nginx/sites-available/myapp

Temel bir reverse proxy yapılandırması şöyle görünür:

upstream nodejs_cluster {
    # PM2 cluster modunda çalışıyorsa hepsi aynı porta geliyor
    server 127.0.0.1:3000;
    
    # Birden fazla port kullanıyorsan:
    # server 127.0.0.1:3000;
    # server 127.0.0.1:3001;
    # server 127.0.0.1:3002;
    
    keepalive 64;
}

server {
    listen 80;
    server_name myapp.example.com;
    
    # Güvenlik header'ları
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "no-referrer-when-downgrade" always;
    
    # Gzip sıkıştırma
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_proxied expired no-cache no-store private auth;
    gzip_types text/plain text/css application/json application/javascript text/xml;
    
    # Statik dosyalar için ayrı location bloğu
    location /static/ {
        alias /var/www/myapp/public/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
    
    # Ana proxy ayarı
    location / {
        proxy_pass http://nodejs_cluster;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
        
        # Timeout ayarları
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
    
    # Loglama
    access_log /var/log/nginx/myapp_access.log;
    error_log /var/log/nginx/myapp_error.log;
}

Yapılandırmayı aktif edelim:

sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

nginx -t komutunu her değişiklikten sonra çalıştır. Syntax hatası varsa seni uyarır, yanlış bir yapılandırmayla servisi reload etmekten kurtarır.

SSL/TLS Yapılandırması

HTTP üzerinden production uygulaması çalıştırmak 2024’te kabul edilemez. Let’s Encrypt ile ücretsiz SSL sertifikası alalım:

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d myapp.example.com

Certbot Nginx yapılandırmanı otomatik olarak günceller. Ama ben elle yapılandırmayı tercih ediyorum, daha fazla kontrol sağlıyor:

sudo nano /etc/nginx/sites-available/myapp

SSL destekli tam yapılandırma:

upstream nodejs_cluster {
    server 127.0.0.1:3000;
    keepalive 64;
}

# HTTP'yi HTTPS'e yönlendir
server {
    listen 80;
    server_name myapp.example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name myapp.example.com;
    
    ssl_certificate /etc/letsencrypt/live/myapp.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/myapp.example.com/privkey.pem;
    
    # Modern SSL ayarları
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    
    # HSTS
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    
    # İstek boyutu limiti
    client_max_body_size 10M;
    
    gzip on;
    gzip_vary on;
    gzip_types text/plain text/css application/json application/javascript;
    
    location /static/ {
        alias /var/www/myapp/public/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
    
    location / {
        proxy_pass http://nodejs_cluster;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
        proxy_connect_timeout 60s;
        proxy_read_timeout 60s;
    }
    
    access_log /var/log/nginx/myapp_access.log;
    error_log /var/log/nginx/myapp_error.log warn;
}

Sertifika otomatik yenileme için cron’u kontrol et:

sudo certbot renew --dry-run
# Cron job zaten eklenmiş olmalı, kontrol et:
sudo systemctl list-timers | grep certbot

WebSocket Desteği

Eğer Socket.io veya benzeri bir WebSocket kütüphanesi kullanıyorsan, Nginx yapılandırmasına özel bir location bloğu eklemen gerekiyor. Standart HTTP proxy ayarları WebSocket bağlantılarını düzgün iletmez:

# WebSocket için özel location bloğu
location /socket.io/ {
    proxy_pass http://nodejs_cluster;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    
    # WebSocket bağlantıları uzun süre açık kalabilir
    proxy_read_timeout 86400s;
    proxy_send_timeout 86400s;
}

Connection "upgrade" ve Upgrade $http_upgrade header’ları WebSocket handshake’i için zorunlu. Bunlar olmadan bağlantı kurulur ama WebSocket moduna geçemez.

Rate Limiting ve Güvenlik

Production ortamında rate limiting olmadan açık kapı bırakmış olursun. Nginx’in yerleşik rate limiting modülü oldukça güçlü:

sudo nano /etc/nginx/nginx.conf

http bloğuna şunu ekle:

# Rate limiting zone tanımları
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=1r/s;
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

Sonra site yapılandırmasında kullan:

location /api/ {
    # Saniyede maksimum 10 istek, burst 20'ye kadar
    limit_req zone=api_limit burst=20 nodelay;
    limit_conn conn_limit 10;
    
    proxy_pass http://nodejs_cluster;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

location /api/auth/login {
    # Login endpoint'i için çok daha sıkı limit
    limit_req zone=login_limit burst=5 nodelay;
    
    proxy_pass http://nodejs_cluster;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Node.js Tarafında IP Adresini Doğru Almak

Nginx reverse proxy arkasında çalışırken req.ip her zaman 127.0.0.1 döner. Gerçek kullanıcı IP’sini almak için Express’te trust proxy ayarını etkinleştirmen lazım:

const express = require('express');
const app = express();

// Nginx reverse proxy için trust proxy ayarı
// '1' sadece ilk proxy'e güvenir, daha güvenli
app.set('trust proxy', 1);

app.get('/ip-test', (req, res) => {
  res.json({
    ip: req.ip,
    ips: req.ips,
    forwarded_for: req.headers['x-forwarded-for'],
    real_ip: req.headers['x-real-ip']
  });
});

Bu ayar olmadan rate limiting kütüphaneleri, loglama ve coğrafi konum tespiti düzgün çalışmaz.

Yaygın Sorunlar ve Çözümleri

Yıllarca bu kurulumları yaparken karşılaştığım en sık sorunları paylaşayım:

502 Bad Gateway hatası: Node.js uygulaması çalışmıyor ya da Nginx ile iletişim kuramıyordur.

# PM2 durumunu kontrol et
pm2 status
pm2 logs myapp --lines 50

# Node.js'in doğru portta dinleyip dinlemediğini kontrol et
ss -tlnp | grep 3000

# Nginx error log'una bak
sudo tail -f /var/log/nginx/myapp_error.log

413 Request Entity Too Large: Upload edilen dosya client_max_body_size limitini aşıyordur. Nginx yapılandırmasında client_max_body_size değerini artır.

Permission denied: Nginx’in statik dosya klasörüne erişim izni olmayabilir.

sudo chown -R www-data:www-data /var/www/myapp/public
sudo chmod -R 755 /var/www/myapp/public

Websocket 101 yerine 400 hatası: proxy_http_version 1.1 ve Upgrade header’larını kontrol et.

Yapılandırmayı Test Etmek

Kurulumun düzgün çalıştığını doğrulamak için birkaç test yapalım:

# HTTP'den HTTPS'e yönlendirme testi
curl -I http://myapp.example.com

# HTTPS endpoint testi
curl -v https://myapp.example.com/health

# Header'ları kontrol et
curl -I https://myapp.example.com | grep -E "X-|Strict|Content"

# SSL sertifika detayları
echo | openssl s_client -connect myapp.example.com:443 2>/dev/null | openssl x509 -noout -dates

# Nginx yapılandırmasını doğrula
sudo nginx -t

# Rate limiting testi (dikkatli kullan)
for i in {1..15}; do curl -s -o /dev/null -w "%{http_code}n" https://myapp.example.com/api/test; done

Log dosyalarını gerçek zamanlı izlemek için:

sudo tail -f /var/log/nginx/myapp_access.log | awk '{print $1, $7, $9}'

Bu komut IP adresi, istek yolu ve HTTP status kodunu gösterir. Sorun ayıklarken çok işe yarar.

Sonuç

Nginx + Node.js + PM2 kombinasyonu, production ortamı için olgun ve battle-tested bir stack. Bu yazıda ele aldığımız konuları özetlersek:

  • Node.js uygulamasını 127.0.0.1‘e bağlamak dışarıdan doğrudan erişimi engeller
  • PM2 cluster modu çok çekirdekli sunucularda performansı maksimize eder
  • upstream bloğu ile yük dengeleme ve keepalive bağlantıları yönetilir
  • SSL/TLS sonlandırma Nginx’te yapılır, Node.js şifrelemeden kurtulur
  • Rate limiting ile brute-force ve DDoS saldırılarına karşı temel koruma sağlanır
  • trust proxy ayarı olmadan gerçek kullanıcı IP’si alınamaz

Bu temel yapıyı oturdurduktan sonra Nginx’in cache modülü, monitoring için Prometheus + Grafana entegrasyonu veya birden fazla Node.js uygulamasını tek sunucuda barındırma gibi konulara geçebilirsin. Ama önce bu temeli sağlam kurman şart, üzerine her şeyi inşa edebilirsin.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir