SSD Nodes Learn 🎉 VPS mulai $5.50/bln
Panduan Matt ConnorOleh Matt Connor

Konfigurasi Reverse Proxy nginx, Dijelaskan

Pelajari konfigurasi reverse proxy nginx di Ubuntu 24.04, termasuk proxy_pass, empat header penting, WebSocket, garis miring penutup, dan unggahan file.

Fungsi konfigurasi reverse proxy nginx

Reverse proxy nginx menerima permintaan yang masuk melalui port 80 dan port 443, lalu meneruskan setiap permintaan ke aplikasi yang sudah listening pada port lokal. Setelah itu, nginx mengembalikan respons aplikasi tersebut ke browser. Konfigurasinya berupa satu blok server, dan blok ini singkat. Hampir seluruh bagian yang sulit terdapat pada lima atau enam baris yang memberi tahu aplikasi siapa klien sebenarnya dan protokol apa yang digunakan klien tersebut.

Semua konfigurasi berikut dibuat dari awal pada Ubuntu 24.04 menggunakan paket nginx dari distribusi. Kondisi awalnya adalah aplikasi yang sudah memberikan respons pada 127.0.0.1:3000. Jika Anda belum menentukan proxy yang akan digunakan, baca terlebih dahulu perbandingan nginx dengan Caddy dan Traefik. Bagian berikut menjelaskan bentuk konfigurasi nginx, baris demi baris.

Jalankan konfigurasi ini di server Anda sendiri. Uji setiap perubahan dengan sudo nginx -t sebelum melakukan reload, lalu baca output yang ditampilkannya.

Lokasi nginx menyimpan konfigurasinya di Ubuntu

sudo apt update
sudo apt install -y nginx
ls -l /etc/nginx/sites-enabled/

File utamanya adalah /etc/nginx/nginx.conf. File ini menetapkan opsi global di dalam blok http { }, lalu memuat dua direktori: /etc/nginx/conf.d/*.conf dan /etc/nginx/sites-enabled/*. Di Ubuntu dan Debian, buat satu file untuk setiap situs di /etc/nginx/sites-available/, lalu aktifkan situs tersebut dengan symlink ke /etc/nginx/sites-enabled/. Menghapus symlink akan menonaktifkan situs tanpa menghapus filenya.

Dua direktif yang digunakan nanti hanya berfungsi dalam konteks http, bukan di dalam blok server: map dan upstream. Letakkan direktif tersebut dalam file tersendiri di bawah /etc/nginx/conf.d/, karena direktori itu dimuat pada tingkat http.

Paket ini menyediakan situs yang sudah diaktifkan dengan nama default. Situs tersebut ditandai sebagai default_server. Artinya, situs ini akan menjawab setiap permintaan yang nilai header Host-nya tidak cocok dengan server_name mana pun dalam konfigurasi Anda. Selama situs ini tetap aktif, permintaan yang tidak cocok dengan nama situs Anda akan diarahkan ke situs tersebut, bukan ke aplikasi Anda. Hapus symlink setelah situs Anda sendiri berfungsi.

sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

Blok server minimal untuk melakukan proxy satu aplikasi

server {
    listen 80;
    listen [::]:80;
    server_name app.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
    }
}

Simpan konfigurasi tersebut sebagai /etc/nginx/sites-available/app.example.com, lalu aktifkan dan muat konfigurasi tersebut.

sudo ln -s /etc/nginx/sites-available/app.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
curl -sI -H 'Host: app.example.com' http://127.0.0.1/

listen 80; mengikat IPv4, sedangkan listen [::]:80; mengikat IPv6. Jika baris kedua dihilangkan dan pencarian DNS (domain name system) pengunjung mengembalikan catatan AAAA untuk server Anda, koneksi akan ditolak. Sementara itu, semua pengguna IPv4 akan melihat situs yang berfungsi. Laporan bug yang Anda terima akan berbunyi, "berfungsi di sisi saya".

server_name dicocokkan dengan header Host yang dikirim browser. Anda dapat mencantumkan beberapa nama dan memisahkannya dengan spasi. Jika tidak ada blok yang cocok, nginx menggunakan blok yang default_server. Karena itu, situs bawaan harus dihapus.

location / adalah pencocokan prefix pada path permintaan, sedangkan / cocok dengan semua path. proxy_pass adalah alamat yang digunakan nginx untuk membuka koneksi. Biarkan aplikasi terikat pada 127.0.0.1 agar satu-satunya jalur masuk adalah melalui nginx. Jika aplikasi berjalan dalam container, publikasikan sebagai 127.0.0.1:3000:3000, bukan 3000:3000, karena Docker menulis aturannya sendiri dan memublikasikan port langsung melewati ufw, sehingga port yang dipublikasikan secara langsung dapat diakses dari Internet, apa pun konfigurasi firewall Anda.

Baris curl mengirim header Host yang benar dari server itu sendiri. Dengan demikian, Anda dapat menguji blok tersebut sebelum DNS mengarah ke mana pun.

Apa yang dikirim nginx ke upstream jika Anda tidak menambahkan konfigurasi lain

proxy_pass saja menyembunyikan empat hal dari aplikasi Anda.

Secara default, nginx menggunakan HTTP/1.0 ke backend dan mengirim Connection: close. Akibatnya, setiap permintaan membuka koneksi upstream baru dan tidak ada upgrade protokol yang dapat dilakukan.

Header Host ditulis ulang menjadi nilai dalam proxy_pass, yaitu 127.0.0.1:3000. Aplikasi yang membuat tautan absolut berdasarkan Host kini menghasilkan tautan yang tidak dapat dibuka oleh siapa pun di luar server.

Koneksi yang mencapai aplikasi berasal dari nginx, sehingga aplikasi melihat alamat klien sebagai 127.0.0.1. Setiap baris log dan setiap pembatasan laju di dalam aplikasi kemudian mencatat proxy, bukan pengunjung.

Aplikasi tidak dapat mengetahui bahwa browser menggunakan HTTPS karena koneksi yang diterimanya adalah HTTP biasa pada alamat loopback.

Empat baris memperbaiki semua masalah tersebut.

Empat header yang harus ditetapkan dan informasi yang dapat dilihat backend dari masing-masing header

location / {
    proxy_pass http://127.0.0.1:3000;

    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;
}

Host membawa nama yang diketik pengunjung. $host adalah nama dari request, dengan port dihapus dan semua huruf diubah menjadi huruf kecil. Tetapkan header ini agar aplikasi membentuk URL absolut dengan benar, seperti redirect setelah login atau tautan dalam email pengaturan ulang kata sandi. Jika header ini tidak ditetapkan, URL tersebut mengarah ke 127.0.0.1:3000, sehingga setelah login browser diarahkan ke alamat yang menolak koneksi. Jika aplikasi juga memerlukan port karena dijalankan pada 8080, gunakan $http_host, yaitu header persis seperti yang dikirim client.

X-Real-IP membawa satu nilai: $remote_addr, yaitu alamat tempat nginx menerima koneksi. Aplikasi membaca nilai ini untuk access log dan rate limiting miliknya sendiri.

X-Forwarded-For membawa sebuah daftar. $proxy_add_x_forwarded_for menambahkan $remote_addr ke nilai yang sudah dikirim client dalam header tersebut, sehingga nilainya dipisahkan dengan koma dan entri yang ditambahkan nginx menjadi entri terakhir. Detail ini menentukan apakah header tersebut dapat dipercaya: client dapat mengirim X-Forwarded-For apa pun yang diinginkannya, sehingga aplikasi yang membaca entri pertama dapat diberi alamat apa saja. Jika nginx adalah edge server, tulis $remote_addr sebagai gantinya dan buang nilai dari client. Jika CDN atau proxy lain berada di depannya, gunakan set_real_ip_from dan real_ip_header dari modul realip, sehingga $remote_addr sendiri menjadi alamat client yang sebenarnya.

X-Forwarded-Proto membawa nilai http atau https. Framework membacanya untuk menentukan apakah cookie harus ditandai Secure dan apakah redirect ke HTTPS harus dipaksakan. Jika header ini tidak ada pada situs TLS dan aplikasi dikonfigurasi untuk memaksakan HTTPS, aplikasi melihat http, merespons dengan redirect ke alamat HTTPS, menerima request berikutnya melalui nginx, tetap melihat http, lalu melakukan redirect lagi. Browser akhirnya berhenti dan menampilkan ERR_TOO_MANY_REDIRECTS.

Mengulangi keempat baris tersebut di setiap location menyebabkan konfigurasinya berbeda-beda. Letakkan semuanya dalam satu file, lalu include file tersebut.

# /etc/nginx/snippets/proxy-headers.conf
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 / {
    include snippets/proxy-headers.conf;
    proxy_pass http://127.0.0.1:3000;
}

Inheritance di sini memiliki jebakan. Sebuah location hanya mewarisi directive proxy_set_header dari server block jika location tersebut tidak mendefinisikan directive sendiri. Tambahkan satu proxy_set_header di dalam location, maka semua header yang didefinisikan pada tingkat server akan dihapus untuk location tersebut. Jadi, letakkan semuanya pada satu tingkat, atau include snippet tersebut di setiap location yang meneruskan request.

Mengapa aplikasi WebSocket saya terhubung lalu terputus?

Karena nilai default melarang upgrade, dan read timeout default menutup tunnel yang tidak aktif setelah 60 detik. WebSocket dimulai sebagai permintaan HTTP yang membawa Upgrade: websocket dan Connection: Upgrade. Keduanya adalah header hop-by-hop. Artinya, proxy seharusnya menggunakan header tersebut, bukan meneruskannya, dan HTTP/1.0 sama sekali tidak memiliki mekanisme upgrade. Keduanya harus ditambahkan kembali secara manual.

Map diletakkan dalam konteks http, di file tersendiri.

# /etc/nginx/conf.d/websocket.conf
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

Berikutnya, konfigurasikan location.

location / {
    include snippets/proxy-headers.conf;
    proxy_pass http://127.0.0.1:3000;

    proxy_http_version 1.1;
    proxy_set_header Upgrade    $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
}

Map tersebut memungkinkan satu location melayani kedua jenis trafik. Pada permintaan biasa, $http_upgrade kosong sehingga $connection_upgrade menjadi close. Pada permintaan upgrade, nilainya adalah websocket sehingga header yang dikirim ke upstream adalah Connection: upgrade. Menetapkan proxy_set_header Connection "upgrade"; secara hard-code akan mengirimkan header tersebut pada setiap permintaan halaman biasa. Beberapa backend merespons permintaan seperti itu dengan status 400.

proxy_read_timeout menyebabkan laporan seperti "halaman dimuat, lalu berhenti diperbarui". Nilai defaultnya adalah 60 detik. Parameter ini mengukur jeda antara dua pembacaan dari backend, bukan durasi koneksi. WebSocket yang tetap diam selama 60 detik akan ditutup oleh nginx, dan konsol browser menampilkan socket yang ditutup dengan kode 1006. Aplikasi yang mengirim heartbeat sendiri lebih sering daripada sekali per menit tidak akan mengalami masalah ini. Aplikasi yang tidak mengirimnya akan terputus tepat setelah satu menit. Masalah ini biasanya pertama kali terlihat pada editor langsung dan dashboard, termasuk instance n8n yang di-host sendiri di balik HTTPS sebagai contoh umum.

Mengapa garis miring di akhir proxy_pass mengubah URL?

Aturannya sederhana. Jika proxy_pass diakhiri URI (uniform resource identifier), bahkan hanya /, nginx menghapus bagian path permintaan yang cocok dengan prefix location, lalu menggantinya dengan URI tersebut. Jika proxy_pass berhenti pada host dan port, path permintaan diteruskan tanpa perubahan.

location /app/ {
    proxy_pass http://127.0.0.1:3000/;
}

Permintaan ke /app/status diteruskan ke backend sebagai /status.

location /app/ {
    proxy_pass http://127.0.0.1:3000;
}

Permintaan ke /app/status diteruskan ke backend sebagai /app/status.

Bentuk yang digunakan bergantung pada aplikasi. Aplikasi dengan pengaturan base-path atau sub-folder memerlukan bentuk kedua, dengan pengaturan tersebut diarahkan ke /app. Aplikasi yang tidak mengenal prefix memerlukan bentuk pertama. Bentuk pertama memiliki dampak yang langsung terlihat: HTML yang dikembalikan aplikasi tetap berisi path absolut seperti /static/main.css, browser meminta path tersebut dari root situs, tidak ada location yang cocok, dan halaman ditampilkan tanpa styling. Tab network pada browser menunjukkan bahwa permintaan aset tersebut menghasilkan 404. Solusinya adalah pengaturan base-path milik aplikasi, atau location /static/ kedua yang mengarah ke backend yang sama.

Location regex tidak boleh memuat URI dalam proxy_pass. sudo nginx -t menolak konfigurasi tersebut dan menyebutkan alasannya: "proxy_pass" cannot have URI part in location given by regular expression, or inside named location, or inside "if" statement, or inside "limit_except" block.

Seluruh masalah ini hilang jika setiap aplikasi memiliki nama sendiri, app.example.com, dan diproxy dari location /. Sub-path hanya layak digunakan jika Anda tidak dapat menambahkan record DNS.

Bagaimana cara menempatkan lebih dari satu backend di balik satu nama?

Gunakan blok upstream. Blok ini berada dalam konteks http. Jadi, tulis blok tersebut di atas blok server dalam file yang sama, atau di /etc/nginx/conf.d/.

upstream app_backend {
    least_conn;
    server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:3001 max_fails=3 fail_timeout=30s;
    keepalive 32;
}

Kemudian, location menentukannya dengan proxy_pass http://app_backend;.

Metode default adalah round robin. least_conn mengirim setiap permintaan ke backend dengan koneksi aktif paling sedikit. Metode ini sesuai untuk permintaan dengan durasi yang berbeda-beda. ip_hash mengikat satu alamat klien ke satu backend. Anda memerlukan ip_hash jika aplikasi menyimpan sesi di memorinya sendiri. Round robin pada dua backend seperti itu dapat mengeluarkan pengguna secara acak ketika permintaan mereka masuk ke instance yang belum pernah menangani permintaan tersebut. Memindahkan sesi ke penyimpanan bersama adalah solusi yang lebih baik.

max_fails=3 fail_timeout=30s berarti tiga percobaan gagal dalam 30 detik akan mengeluarkan server tersebut selama 30 detik. Jika semua server dalam blok berada dalam kondisi itu, klien menerima 502 dan error log berisi no live upstreams while connecting to upstream.

keepalive 32 mempertahankan hingga 32 koneksi idle ke backend tetap terbuka untuk setiap proses worker. Dengan demikian, sebagian besar permintaan tidak perlu melakukan TCP handshake. Fitur ini hanya berfungsi dengan proxy_http_version 1.1 dan tanpa Connection: close yang diteruskan ke upstream. Jika location yang sama juga menggunakan WebSocket map, ubah kasus kosong dari close menjadi string kosong. Dengan begitu, permintaan biasa tidak membawa header Connection dan koneksi dalam pool dapat digunakan kembali.

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      '';
}

Nama di dalam blok upstream di-resolve saat nginx dimulai. Jika backend Anda berupa container yang menerima alamat baru saat dimulai ulang, nginx akan terus menggunakan alamat lama sampai Anda me-reload-nya. Dalam jaringan Docker, Anda dapat memindahkan proses lookup ke waktu permintaan dengan embedded resolver.

resolver 127.0.0.11 valid=10s;
set $backend http://app:3000;
proxy_pass $backend;

Jika container sering dibuat dan dihapus hingga Anda harus terus mengedit nginx, proxy yang membaca label container merupakan pilihan yang lebih baik. Traefik di depan beberapa aplikasi Docker Compose membuat route berdasarkan container itu sendiri.

Mengapa unggahan gagal dengan 413 Request Entity Too Large?

client_max_body_size secara default bernilai 1 megabyte. nginx menolak body request yang lebih besar sebelum aplikasi Anda menerima bagian apa pun, dan error log mencatat client intended to send too large body. Tingkatkan nilainya di server block, atau di location tempat unggahan berlangsung.

client_max_body_size 512m;

Nilai 0 menonaktifkan pemeriksaan sepenuhnya. Aplikasi juga memiliki batasnya sendiri. Jadi, jika error 413 tetap muncul setelah perubahan ini, sumbernya adalah backend. Periksa pengaturan unggahan aplikasi berikutnya.

Secara default, nginx membaca seluruh body request sebelum membuka koneksi upstream. Data berukuran besar terlebih dahulu ditulis ke file sementara di disk. Cara ini melindungi aplikasi dari client yang lambat karena backend menerima unggahan dengan kecepatan lokal penuh. Untuk unggahan yang sangat besar, Anda dapat menggunakan streaming.

proxy_request_buffering off;

Backend kemudian menerima body saat data tiba dan harus mampu menanganinya. nginx juga tidak lagi dapat mencoba ulang request ke upstream lain karena body tersebut sudah tidak tersedia.

client_body_timeout, yang secara default bernilai 60 seconds, berlaku di antara dua pembacaan body berturut-turut, bukan untuk keseluruhan unggahan. Unggahan yang lambat tetapi stabil dapat melewati batas ini. Unggahan yang berhenti akan dihentikan.

Buffer respons dan pengaturan yang mengganggu output langsung

proxy_buffering aktif secara default dan biasanya itulah yang Anda perlukan. nginx membaca respons dari aplikasi secepat aplikasi dapat menulisnya, menahannya, lalu mengirimkannya kepada klien lambat sesuai kecepatan klien tersebut. Worker aplikasi selesai lebih awal dan tidak terus sibuk selama proses pengunduhan yang lambat.

Pengaturan ini mengganggu respons streaming. Server-sent events dan output log langsung tidak menampilkan apa pun kepada pembaca hingga buffer terisi. Nonaktifkan buffering hanya pada lokasi tersebut.

proxy_buffering off;

Jika Anda mengendalikan aplikasinya, langkah yang lebih baik adalah mengirim header X-Accel-Buffering: no hanya pada respons streaming. nginx membaca header tersebut untuk setiap respons dan menonaktifkan buffering hanya untuk respons itu, sehingga halaman biasa tetap memperoleh manfaatnya.

Jika error log menampilkan upstream sent too big header while reading response header from upstream, header respons tidak muat dalam satu buffer. proxy_buffer_size secara default berukuran satu halaman memori, yaitu 4 atau 8 kilobyte, bergantung pada platform. Cookie yang panjang atau header autentikasi yang besar dapat melampaui ukuran tersebut. Tingkatkan kedua nilainya.

proxy_buffer_size 16k;
proxy_buffers 8 16k;

TLS berada di bagian mana dalam konfigurasi ini?

Pada nginx, di depan semua komponen di atas. TLS (transport layer security) berakhir di proxy, sedangkan koneksi dari nginx ke aplikasi tetap menggunakan HTTP biasa melalui alamat loopback. Tidak ada perangkat lain di jaringan yang dapat membacanya. Aplikasi mengetahui bahwa pengunjung menggunakan HTTPS dari X-Forwarded-Proto, yaitu header keempat dari empat header.

Jangan menulis path sertifikat secara manual. Arahkan record DNS ke server, buka firewall, lalu biarkan Certbot mengedit server block yang sama. Certbot menambahkan baris listen 443 ssl dengan path ssl_certificate, serta pengalihan dari port 80. Menerbitkan sertifikat Let's Encrypt untuk nginx dengan Certbot menjelaskan proses penerbitan dan timer pembaruan.

sudo ufw allow 'Nginx Full'
sudo ufw status

Nginx Full adalah profil aplikasi yang dipasang oleh paket nginx. Profil ini membuka port 80 dan port 443 secara bersamaan. Port 80 harus tetap terbuka untuk tantangan pembaruan HTTP-01, meskipun semua pengunjung sudah dialihkan ke HTTPS.

Uji konfigurasi, lalu lakukan reload

sudo nginx -t
sudo systemctl reload nginx

nginx -t mem-parsing setiap file yang disertakan, lalu melaporkan bahwa pengujian berhasil atau menampilkan file dan baris tempat proses berhenti. Baca output tersebut sebelum melakukan reload. Reload dengan konfigurasi yang rusak tidak diterapkan: nginx tetap melayani menggunakan konfigurasi sebelumnya, sehingga situs tetap aktif sementara perubahan Anda tidak melakukan apa pun tanpa pesan. systemctl restart berperilaku berbeda dan lebih buruk, karena restart terlebih dahulu menghentikan server yang sedang berjalan. Akibatnya, kesalahan konfigurasi membuat nginx tidak berjalan sama sekali. Secara default, gunakan reload, dan gunakan restart hanya untuk perubahan yang memang memerlukannya.

sudo tail -f /var/log/nginx/error.log
sudo ss -lntp | grep -E ':(80|443|3000)'

Baris ss menampilkan proses yang menggunakan setiap port, sehingga Anda dapat memastikan aplikasi benar-benar listening pada alamat yang ditunjuk proxy_pass.

Kegagalan yang benar-benar akan Anda temui

502 Bad Gateway, dengan connect() failed (111: Connection refused) while connecting to upstream di log error. Tidak ada proses yang listening pada alamat di proxy_pass. Aplikasi berhenti, terikat ke port lain, atau terikat ke alamat internal container yang tidak dapat dijangkau oleh host.

502 dengan no live upstreams while connecting to upstream. Semua server dalam blok upstream saat ini ditandai gagal oleh max_fails. Perbaiki backend tersebut. nginx akan mencoba ulang setelah fail_timeout berakhir.

504 Gateway Time-out, dengan upstream timed out (110: Connection timed out) while reading response header from upstream. Backend menerima koneksi, lalu tidak mengirim apa pun selama proxy_read_timeout detik. Menaikkan timeout tepat untuk laporan yang memang lambat, tetapi tidak tepat untuk aplikasi yang macet.

Setiap path mengembalikan 404 dari aplikasi. Aturan trailing slash menulis ulang path tersebut. Bandingkan path yang dicatat dalam log aplikasi dengan path yang Anda minta.

Situs yang berbeda merespons. server_name tidak cocok dengan header Host, sehingga permintaan masuk ke blok default_server.

Halaman dimuat, lalu antarmuka berhenti merespons setelah sekitar satu menit. Ini adalah kasus WebSocket: konfigurasi Upgrade belum ada, atau proxy_read_timeout masih bernilai 60 detik.

FAQ

Mengapa nginx mengembalikan 502 Bad Gateway setelah saya menambahkan proxy_pass?

nginx tidak dapat membuka koneksi ke alamat pada proxy_pass. Log error di /var/log/nginx/error.log menyebutkan penyebabnya: connect() failed (111: Connection refused) while connecting to upstream berarti tidak ada proses yang sedang listen di sana, sedangkan no live upstreams berarti setiap server dalam blok upstream telah ditandai gagal. Jalankan sudo ss -lntp | grep 3000 untuk melihat proses yang menggunakan port tersebut dan alamat tempat proses itu terikat. Aplikasi yang terikat pada alamat internal container, atau pada port yang berbeda dari yang Anda tulis, selalu menghasilkan error ini.

Mengapa aplikasi saya terputus setelah sekitar satu menit di belakang nginx?

Koneksi tersebut adalah WebSocket, dan proxy_read_timeout masih menggunakan nilai default 60 detik. Nilai ini mengukur jeda antara dua pembacaan dari backend. Socket yang tidak menerima data akan ditutup oleh nginx, lalu console browser melaporkan kode penutupan 1006. Atur proxy_http_version 1.1, teruskan Upgrade dan Connection melalui map pada $http_upgrade, lalu naikkan proxy_read_timeout menjadi sekitar 3600s. Tanpa header Upgrade, proses upgrade tidak pernah terjadi. Akibatnya, aplikasi beralih ke polling atau tidak menampilkan pembaruan langsung.

Apakah garis miring di akhir proxy_pass berpengaruh?

Ya, dan garis miring tersebut mengubah path yang diterima backend. Dengan location /app/ dan proxy_pass http://127.0.0.1:3000/, permintaan ke /app/status tiba di backend sebagai /status, karena URI apa pun setelah host dan port menggantikan prefix location yang cocok. Hapus garis miring terakhir tersebut, dan permintaan yang sama tiba sebagai /app/status. Penghapusan prefix sering merusak link aset milik aplikasi. Link tersebut tetap absolut dan kemudian menghasilkan 404 di root situs. Karena itu, aplikasi dengan pengaturan base-path lebih cocok menggunakan bentuk yang meneruskan path tersebut.

Mengapa log aplikasi saya mencatat 127.0.0.1 sebagai alamat IP setiap pengunjung?

Karena koneksi yang diterima aplikasi memang berasal dari nginx pada alamat loopback. Alamat pengunjung hanya diteruskan ke aplikasi melalui header yang Anda atur: proxy_set_header X-Real-IP $remote_addr; untuk satu nilai dan proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; untuk rantai alamat yang ditambahkan. Aplikasi kemudian harus dikonfigurasi agar mempercayai header tersebut. Ingat bahwa client dapat mengirim X-Forwarded-For miliknya sendiri. Karena itu, saat nginx menjadi edge server, timpa nilainya dengan $remote_addr, bukan menambahkannya.

Apakah saya memerlukan TLS pada koneksi antara nginx dan aplikasi?

Tidak, jika aplikasi berjalan pada server yang sama dan terikat pada 127.0.0.1, karena trafik tersebut tidak pernah meninggalkan mesin. Hentikan TLS di nginx, pertahankan proxy_pass menggunakan HTTP biasa melalui loopback, lalu kirim X-Forwarded-Proto $scheme agar aplikasi mengetahui bahwa pengunjung menggunakan HTTPS. Jika backend berada pada host lain melalui jaringan yang tidak Anda kendalikan, hop tersebut memerlukan perlindungannya sendiri. Gunakan HTTPS ke backend atau tunnel privat antara kedua mesin.