Node.js App #
Caddy sangat cocok sebagai reverse proxy untuk aplikasi Node.js. Caddy menangani TLS, HTTP/2, dan HTTPS otomatis sementara aplikasi Node.js fokus pada logika bisnis. Ini adalah salah satu kombinasi yang paling populer untuk deployment modern.
Arsitektur Deployment #
Internet
│ HTTPS (443)
▼
Caddy
│ HTTP (internal)
▼
Node.js App (port 3000)
│
▼
Database / Services
Konfigurasi Dasar #
example.com {
reverse_proxy localhost:3000
}
Hanya satu baris — Caddy otomatis handle HTTPS, HTTP/2, sertifikat TLS, dan redirect HTTP ke HTTPS.
Konfigurasi Lengkap dengan Best Practices #
example.com {
# Logging akses
log {
output file /var/log/caddy/nodejs-access.log {
roll_size 100mb
roll_keep 7
roll_keep_days 30
}
format json
}
# Kompresi response
encode gzip zstd
# Security headers
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "strict-origin-when-cross-origin"
-Server
-X-Powered-By
}
# Sajikan aset statis langsung dari Caddy (lebih efisien)
@static path /static/* /favicon.ico /robots.txt
handle @static {
root * /var/www/myapp
file_server {
# Cache lama untuk aset dengan hash di nama file
pass_thru
}
header Cache-Control "public, max-age=31536000, immutable"
}
# Semua request lain ke Node.js
handle {
reverse_proxy localhost:3000 {
# Forward IP asli client
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
header_up Host {host}
# Health check untuk memastikan backend hidup
health_uri /health
health_interval 10s
health_timeout 5s
health_status 200
# Timeout settings
transport http {
dial_timeout 5s
response_header_timeout 60s
read_timeout 120s
write_timeout 120s
}
}
}
}
Node.js dengan PM2 #
PM2 adalah process manager yang menjaga Node.js tetap berjalan dan restart otomatis:
# Install PM2
npm install -g pm2
# Jalankan aplikasi
pm2 start app.js --name myapp
# Atau dengan ecosystem file
// ecosystem.config.js
module.exports = {
apps: [{
name: 'myapp',
script: 'dist/server.js',
instances: 'max', // Gunakan semua CPU core
exec_mode: 'cluster', // Cluster mode untuk load balancing
env: {
NODE_ENV: 'development',
PORT: 3000
},
env_production: {
NODE_ENV: 'production',
PORT: 3000,
DB_URL: process.env.DB_URL
},
// Logging
error_file: '/var/log/myapp/error.log',
out_file: '/var/log/myapp/access.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
// Restart policy
max_restarts: 10,
min_uptime: '10s',
// Memory limit — restart jika melebihi
max_memory_restart: '500M'
}]
};
pm2 start ecosystem.config.js --env production
pm2 save
pm2 startup # Auto-start setelah reboot
Multi-Instance Load Balancing #
example.com {
encode gzip zstd
reverse_proxy {
# Multiple Node.js instances untuk load balancing
to localhost:3000 localhost:3001 localhost:3002 localhost:3003
lb_policy round_robin
health_uri /health
health_interval 10s
# Passive health — tandai down setelah 3 kali gagal
fail_duration 30s
max_fails 3
header_up X-Real-IP {remote_host}
header_up X-Forwarded-Proto {scheme}
}
}
Node.js dengan WebSocket #
Express + Socket.io atau similar memerlukan upgrade koneksi ke WebSocket:
example.com {
# Regular HTTP requests
handle /api/* {
reverse_proxy localhost:3000
}
# WebSocket connections
handle /socket.io/* {
reverse_proxy localhost:3000 {
# Caddy otomatis handle WebSocket upgrade
# Tidak perlu konfigurasi khusus!
}
}
# Static files
handle {
root * /var/www/myapp/public
file_server
}
}
Zero-Downtime Deployment dengan Node.js #
#!/bin/bash
# deploy-nodejs.sh
APP_DIR="/var/www/myapp"
CADDY_API="http://localhost:2019"
echo "=== Zero-Downtime Node.js Deployment ==="
# 1. Pull kode terbaru
cd "$APP_DIR"
git pull origin main
# 2. Install dependencies
npm ci --production
# 3. Build (jika TypeScript atau bundler)
npm run build
# 4. Reload PM2 dengan zero-downtime
# PM2 cluster mode melakukan rolling restart
pm2 reload ecosystem.config.js --env production
# 5. Tunggu sampai semua instance healthy
sleep 5
# 6. Verifikasi
APP_STATUS=$(pm2 list | grep myapp | grep online)
if [ -z "$APP_STATUS" ]; then
echo "✗ App tidak berjalan!"
exit 1
fi
# 7. Health check via Caddy
HTTP_STATUS=$(curl -sf -o /dev/null -w "%{http_code}" https://example.com/health)
if [ "$HTTP_STATUS" != "200" ]; then
echo "✗ Health check gagal! Status: $HTTP_STATUS"
exit 1
fi
echo "✓ Deployment berhasil!"
Express.js — Konfigurasi yang Diperlukan #
// app.js — setting yang penting untuk deployment di belakang proxy
const express = require('express');
const app = express();
// PENTING: Trust proxy header dari Caddy
// Ini memungkinkan req.ip mengembalikan IP asli client
app.set('trust proxy', 1);
// Health check endpoint (diperlukan untuk Caddy health check)
app.get('/health', (req, res) => {
res.json({
status: 'ok',
uptime: process.uptime(),
timestamp: new Date().toISOString()
});
});
// Middleware: log request dengan IP asli
app.use((req, res, next) => {
// Dengan trust proxy, req.ip adalah IP asli client (dari X-Real-IP)
console.log(`${req.ip} ${req.method} ${req.path}`);
next();
});
app.listen(3000, '127.0.0.1', () => {
// Bind ke localhost saja — Caddy yang expose ke internet
console.log('App listening on localhost:3000');
});
Monitoring dan Alerting #
# Monitor PM2 status
pm2 status
pm2 monit # Real-time monitoring
# Cek error log
pm2 logs myapp --err --lines 50
# Setup Caddy health check monitoring
watch -n 30 'curl -sf https://example.com/health | jq .'
# Script alert sederhana
#!/bin/bash
HEALTH=$(curl -sf https://example.com/health -o /dev/null -w "%{http_code}" 2>/dev/null)
if [ "$HEALTH" != "200" ]; then
# Kirim notifikasi
curl -X POST "https://hooks.slack.com/services/..." \
-H "Content-Type: application/json" \
-d '{"text": "🚨 Production down! Health check failed."}'
fi
Ringkasan #
- Cukup satu baris
reverse_proxy localhost:3000untuk konfigurasi dasar — Caddy otomatis handle TLS, HTTP/2, dan redirect.- Selalu tambahkan
header_up X-Real-IP {remote_host}dan setapp.set('trust proxy', 1)di Express agar aplikasi mendapatkan IP asli client.- Gunakan PM2 cluster mode untuk memanfaatkan semua CPU core dan melakukan zero-downtime reload.
- Sajikan aset statis (JS, CSS, gambar) langsung dari Caddy tanpa melewati Node.js — lebih efisien dan mengurangi beban aplikasi.
- Tambahkan
/healthendpoint di aplikasi dan konfigurasikan Caddyhealth_uriuntuk pemantauan otomatis dan traffic failover.- Bind Node.js ke
127.0.0.1bukan0.0.0.0— hanya Caddy yang boleh mengakses port 3000, bukan internet langsung.
← Sebelumnya: Buat Plugin Berikutnya: PHP-FPM →
Konfigurasi CORS untuk Node.js API #
api.example.com {
@options method OPTIONS
handle @options {
header Access-Control-Allow-Origin "https://app.example.com"
header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
header Access-Control-Allow-Headers "Content-Type, Authorization"
header Access-Control-Allow-Credentials "true"
header Access-Control-Max-Age "86400"
respond "" 204
}
header Access-Control-Allow-Origin "https://app.example.com"
header Access-Control-Allow-Credentials "true"
encode gzip zstd
reverse_proxy localhost:3000 {
header_up X-Real-IP {remote_host}
header_up X-Forwarded-Proto {scheme}
health_uri /health
health_interval 10s
}
}
Dengan mengkonfigurasi CORS di Caddy (bukan di aplikasi Node.js), kamu cukup mengkonfigurasi di satu tempat meski punya banyak service Node.js di belakang gateway yang sama.
Streaming Response dari Node.js #
// app.js — contoh streaming response
app.get('/stream', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// Kirim event setiap detik
const interval = setInterval(() => {
res.write(`data: ${JSON.stringify({ time: new Date() })}\n\n`);
}, 1000);
req.on('close', () => {
clearInterval(interval);
});
});
example.com {
reverse_proxy localhost:3000 {
# Untuk Server-Sent Events: nonaktifkan buffering
flush_interval -1 # Flush segera, tidak buffer
}
}