Deploy Aplikasi Node.js di VPS dengan PM2 dan Nginx

Kenapa kombinasi ini

Menjalankan node app.js langsung dari terminal itu rapuh: begitu terminal ditutup atau server reboot, aplikasi ikut mati. PM2 menyelesaikan sisi proses — ia menjaga aplikasi tetap jalan, me-restart saat crash, dan menghidupkannya lagi setelah reboot. Nginx menyelesaikan sisi akses — ia menerima trafik dari internet (port 80/443) lalu meneruskannya ke aplikasi yang berjalan di port internal seperti 3000. Keduanya sudah teruji bertahun-tahun dan tidak butuh panel tambahan.

Asumsi di artikel ini: Anda sudah punya VPS Ubuntu 24.04 dengan akses root atau sudo, dan sebuah domain yang diarahkan (record A) ke IP VPS. Contoh aplikasi memakai port 3000 dan domain app.contoh.com — ganti sesuai punya Anda.

1. Pasang Node.js

Ubuntu 24.04 membawa Node.js 18 di repositori bawaan, tapi versi itu sudah tua untuk banyak proyek modern. Lebih aman memakai Node.js 22 LTS dari NodeSource:

sudo apt update && sudo apt install -y curl
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs
node -v && npm -v

Perintah kedua menambahkan repositori NodeSource, lalu perintah ketiga memasang node sekaligus npm. Verifikasi dengan node -v — harus keluar v22.x.

2. Siapkan aplikasi

Masuk ke direktori proyek, misalnya /var/www/app. Contoh minimal dengan modul bawaan Node (tanpa dependency) supaya bisa langsung dicoba:

sudo mkdir -p /var/www/app
sudo chown -R "$USER:$USER" /var/www/app
cd /var/www/app
// app.js
const http = require('http');
const port = process.env.PORT || 3000;

http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('OK dari Node.js\n');
}).listen(port, '127.0.0.1', () => {
  console.log(`Jalan di port ${port}`);
});

Perhatikan listen diikat ke 127.0.0.1, bukan 0.0.0.0. Aplikasi tidak perlu terekspos langsung ke internet — hanya Nginx yang boleh meneruskan trafik ke sana. Ini satu baris kecil yang sering dilupakan dan berdampak besar pada keamanan.

Untuk proyek nyata (misalnya Express), langkahnya sama: npm ci atau npm install --omit=dev di direktori proyek, lalu pastikan ada perintah start yang jelas di package.json.

3. Jalankan dengan PM2

Pasang PM2 secara global, lalu buat file ecosystem agar konfigurasinya tersimpan dan bisa diulang:

sudo npm install -g pm2
// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'app',
    script: './app.js',
    instances: 1,
    exec_mode: 'fork',
    env: {
      NODE_ENV: 'production',
      PORT: 3000,
    },
    max_memory_restart: '500M',
    error_file: './logs/err.log',
    out_file: './logs/out.log',
    time: true,
  }],
};

Lalu jalankan:

cd /var/www/app
mkdir -p logs
pm2 start ecosystem.config.js
pm2 status

pm2 status menampilkan nama, status online, dan pemakaian memori. max_memory_restart adalah pengaman: kalau aplikasi bocor memori sampai 500 MB, PM2 me-restart-nya otomatis.

4. Restart otomatis saat server reboot

Ini langkah yang paling sering terlewat. Tanpa ini, sekali VPS reboot (misalnya setelah update kernel), aplikasi diam-diam mati sampai Anda sadar:

pm2 save
pm2 startup systemd

pm2 startup akan mencetak satu perintah sudo env ... — jalankan perintah itu persis seperti yang tampil, karena isinya menyesuaikan user dan path di server Anda. Setelah itu, uji dengan sudo reboot, tunggu server kembali, lalu cek pm2 status. Kalau aplikasi kembali online sendiri, konfigurasi sudah benar.

5. Nginx sebagai reverse proxy

Pasang Nginx dan buat konfigurasi site:

sudo apt install -y nginx
# /etc/nginx/sites-available/app
server {
    listen 80;
    server_name app.contoh.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        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;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
    }
}

Header X-Forwarded-* penting: tanpa itu aplikasi akan mengira semua pengunjung berasal dari IP 127.0.0.1, dan log maupun rate limit jadi tidak berguna. Aktifkan site-nya dan uji konfigurasi sebelum reload:

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

nginx -t wajib dijalankan sebelum reload — kalau ada salah ketik di konfigurasi, Anda ketahuan sebelum Nginx yang sedang jalan ikut terganggu. Setelah ini, http://app.contoh.com sudah menampilkan aplikasi.

6. HTTPS dengan Certbot (opsional tapi dianjurkan)

Begitu domain sudah mengarah dan Nginx jalan, HTTPS tinggal satu perintah:

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d app.contoh.com

Certbot akan mengubah konfigurasi Nginx secara otomatis dan memasang timer perpanjangan sertifikat. Uji perpanjangannya dengan sudo certbot renew --dry-run. Satu catatan: jangan mengedit blok server hasil Certbot secara sembarangan — bagian yang ditandai "managed by Certbot" akan ditulis ulang saat perpanjangan.

7. Log dan update sehari-hari

Beberapa perintah yang akan sering Anda pakai:

pm2 logs app --lines 100   # lihat 100 baris log terakhir
pm2 monit                  # monitor CPU dan memori interaktif
pm2 restart app            # restart setelah perbaikan kecil
pm2 reload app             # reload dengan downtime minimal

Alur update versi baru cukup seperti ini:

cd /var/www/app
git pull
npm ci --omit=dev
pm2 reload ecosystem.config.js

reload (bukan restart) lebih halus karena PM2 menunggu proses lama siap diganti sebelum mematikannya. Untuk log jangka panjang, pasang modul rotasi agar disk tidak penuh oleh log:

pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7

Ini membatasi tiap file log 10 MB dan menyimpan 7 arsip terakhir.

Jebakan yang sering terjadi

Port sudah dipakai. Kalau pm2 start gagal dengan EADDRINUSE, ada proses lain di port 3000. Cari dengan ss -tlnp | grep 3000, matikan proses liarnya, baru start ulang.

Aplikasi jalan tapi domain 502 Bad Gateway. Biasanya aplikasi belum benar-benar jalan (pm2 status menunjukkan errored) atau proxy_pass salah port. Cek pm2 logs dulu, baru konfigurasi Nginx.

Aplikasi mati setelah reboot. Hampir selalu karena pm2 save belum dijalankan setelah pm2 start, atau perintah dari pm2 startup belum dieksekusi.

Permission file. Kalau aplikasi dijalankan sebagai user biasa tapi file proyek milik root (atau sebaliknya), PM2 gagal menulis log. Samakan kepemilikan dengan sudo chown -R $USER:$USER /var/www/app sebelum start.

Checklist penutup

  • Node.js 22 terpasang dan node -v terverifikasi
  • Aplikasi mengikat 127.0.0.1, bukan 0.0.0.0
  • pm2 start ecosystem.config.js berstatus online
  • pm2 save dan perintah pm2 startup sudah dijalankan, teruji dengan reboot
  • Nginx nginx -t lolos dan site aktif, domain menampilkan aplikasi
  • HTTPS aktif dan certbot renew --dry-run berhasil
  • Log berotasi dan alur update (pull → npm ci → pm2 reload) sudah dicoba sekali

Dengan susunan ini, satu VPS kecil sudah cukup menjalankan aplikasi Node.js produksi yang stabil — tanpa panel, tanpa biaya tambahan, dan dengan alur perawatan yang bisa Anda ulang untuk proyek berikutnya.

Komentar