Skip to main content
BlogDevOps

Cara Setting Nginx Reverse Proxy untuk Docker di Cloudflare

Panduan pemula cara setting nginx reverse proxy untuk app Docker di balik Cloudflare: proxy_pass, header, mode SSL, IP asli, dan tabel error 502/504.

Matthews Wong

Software Engineer

Oktober 202610 menit baca
Cara Setting Nginx Reverse Proxy untuk Docker di Cloudflare

Ringkasan Utama

Cara setting nginx reverse proxy untuk aplikasi Docker: ikat container ke 127.0.0.1, tulis server block dengan proxy_pass ke port itu, teruskan header Host dan X-Forwarded-For, lalu uji dengan nginx -t sebelum reload. Di belakang Cloudflare, pakai mode SSL Full (strict) dan set_real_ip_from agar tidak ada redirect loop dan IP pengunjung tetap asli.

Aplikasimu sudah jalan di container dan bisa dibuka lewat 203.0.113.10:3000. Lalu kamu mengarahkan domain, dan yang muncul adalah halaman Welcome to nginx!, 502 Bad Gateway, atau halaman yang berputar sampai browser menyerah. Di titik ini kebanyakan pemula sadar bahwa memasang nginx baru separuh pekerjaan. Separuh lainnya adalah menyambungkan nginx ke container, ke domain, dan ke Cloudflare dengan benar.

Panduan ini memakai pola yang sama dengan situs ini: docker-compose.yml mengikat aplikasi ke 127.0.0.1 dengan komentar bahwa nginx yang menangani TLS dan meneruskan request ke port itu. Konfigurasi nginx situs ini sendiri tidak ada di repository, jadi semua blok di bawah bersumber dari dokumentasi nginx.org dan Cloudflare, bukan salinan file server saya. Ikuti enam langkahnya berurutan, lalu pakai tabel error di akhir kalau ada yang tidak jalan.

Ingin paham konsepnya dulu?

Panduan ini fokus pada langkah dan error. Kalau kamu lebih suka belajar urutan dari konsep ke praktik, lesson gratis kami membahas reverse proxy, header, dan cache di Cloudflare: Reverse Proxy Nginx dan Docker Compose di Balik Cloudflare. Lesson itu juga punya latihan yang bisa dijalankan di laptop tanpa server.

Yang kamu butuhkan

Siapkan empat hal ini sebelum mulai. Semua nama di bawah adalah placeholder, ganti dengan milikmu.

  • Sebuah server Linux (Debian atau Ubuntu) dengan akses sudo, misalnya dengan user bernama deploy dan alamat 203.0.113.10.
  • Docker dan Docker Compose sudah terpasang, dan aplikasimu sudah punya image yang bisa dijalankan (di sini bernama my-app:latest, mendengarkan di port 3000).
  • Sebuah domain, di sini example.com, yang DNS-nya dikelola di Cloudflare.
  • Port 80 dan 443 terbuka di firewall server.

Langkah 1: Jalankan app di container, tapi hanya di loopback

Aturan pertama: container tidak boleh bisa dijangkau langsung dari internet. Di docker-compose.yml situs ini, port aplikasi diikat ke 127.0.0.1 supaya satu-satunya jalan masuk dari luar adalah lewat nginx. Pola yang sama berlaku untuk appmu. Di sisi kiri titik dua pada ports kamu menentukan alamat dan port di host, di sisi kanan port di dalam container.

# docker-compose.yml
services:
  web:
    image: my-app:latest
    restart: unless-stopped
    ports:
      # Wrong: "3000:3000" publishes on every interface, so anyone can skip nginx
      # Right: left side = host address + port, right side = port in the container
      - "127.0.0.1:3000:3000"

# Start it, then check the PORTS column and the app itself
docker compose up -d
docker compose ps
curl -I http://127.0.0.1:3000

Output yang diharapkan: Output yang diharapkan dari docker compose ps: kolom PORTS menampilkan 127.0.0.1:3000->3000/tcp, bukan 0.0.0.0:3000. Lalu curl -I ke alamat itu membalas status 200 (atau 3xx kalau app-mu me-redirect).

Kesalahan paling umum di langkah ini adalah menulis 3000:3000 tanpa alamat. Port jadi terbuka di semua interface, siapa pun bisa melewati nginx dan Cloudflare, dan semua pengaturan keamanan yang kamu buat di langkah berikutnya tidak berlaku untuk jalur itu.

Langkah 2: Pasang nginx dan pastikan hidup

Di Debian atau Ubuntu, nginx tersedia dari repository bawaan. Setelah terpasang, service-nya langsung berjalan dan melayani halaman sambutan di port 80. Itu justru berguna: kalau halaman itu muncul, berarti nginx sehat dan sisa masalah ada di konfigurasimu.

sudo apt install -y nginx
nginx -v
sudo systemctl status nginx
curl -I http://127.0.0.1

Output yang diharapkan: Output yang diharapkan: nginx -v mencetak nginx version: nginx/ diikuti nomor versi yang terpasang, systemctl status menampilkan active (running), dan curl ke 127.0.0.1 membalas 200 dengan isi halaman Welcome to nginx!.

Langkah 3: Tulis server block dengan proxy_pass

Reverse proxy di nginx pada dasarnya satu directive: proxy_pass, yang menentukan protokol dan alamat server tujuan. Letakkan di dalam location di sebuah server block. File konfigurasi utama nginx.conf biasanya berada di /etc/nginx, dan paket Debian atau Ubuntu memuat file di conf.d dari sana. Pastikan dengan nginx -T seperti di bawah, karena tata letak bisa berbeda antar distro.

# See which files this nginx really loads (layout differs per distro)
sudo nginx -T | grep include

# /etc/nginx/conf.d/example.com.conf
server {
    listen 80;
    server_name example.com www.example.com;

    location / {
        # No trailing slash and no path: the client's URI is passed on unchanged
        proxy_pass http://127.0.0.1:3000;
    }
}

# Test first, reload second. A failed reload keeps the old config running.
sudo nginx -t
sudo nginx -s reload

# Fake the domain locally to prove the server block matches
curl -I -H "Host: example.com" http://127.0.0.1

Selalu jalankan nginx -t sebelum reload. Perintah itu memeriksa sintaks dan mencoba membuka file yang disebut di konfigurasi. Saat reload, master process memeriksa konfigurasi baru dulu; kalau gagal, nginx membatalkan perubahan dan tetap memakai konfigurasi lama, jadi reload yang salah tidak menjatuhkan situsmu.

Output yang diharapkan: Output yang diharapkan: nginx -t mencetak dua baris yang berakhir dengan syntax is ok dan test is successful. Setelah reload, curl dengan header Host example.com ke 127.0.0.1 membalas persis seperti app-mu, bukan halaman sambutan.

Langkah 4: Teruskan header yang benar

Secara default nginx tidak meneruskan header Host dari request asli. Ia mengirim Host berisi alamat upstream-nya sendiri, yaitu 127.0.0.1:3000. Akibatnya app membuat link absolut, cookie, atau redirect ke alamat itu, dan semuanya rusak di browser pengunjung. Tambahkan empat baris proxy_set_header agar app tahu domain, IP, dan protokol yang sebenarnya.

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

    # Wrong: leave these out and the app sees Host: 127.0.0.1:3000
    # Right: pass what the visitor asked for
    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;
}

# Trailing slash trap
# Wrong (if the app expects /api/users): /api/users becomes /users
location /api/ {
    proxy_pass http://127.0.0.1:3000/;
}
# Right: no URI after the port, the path is forwarded as sent
location /api/ {
    proxy_pass http://127.0.0.1:3000;
}

Dua jebakan lagi. Pertama, proxy_set_header hanya diwarisi dari level di atasnya kalau tidak ada proxy_set_header sama sekali di level saat ini, jadi jangan menyebar header di server dan location sekaligus. Kedua, garis miring di akhir proxy_pass mengubah arti: tanpa path, URI dari klien diteruskan apa adanya; dengan path atau garis miring, bagian yang cocok dengan location diganti.

Langkah 5: Sambungkan domain lewat Cloudflare dan pilih mode SSL yang benar

Di dashboard Cloudflare, buat record A untuk example.com ke 203.0.113.10 dengan proxy aktif. Lalu buka pengaturan SSL/TLS dan pilih mode. Menurut dokumentasi Cloudflare, mode Flexible mengenkripsi pengunjung ke Cloudflare saja, sedangkan Cloudflare ke origin tetap HTTP. Full (strict) memvalidasi sertifikat di origin, yang bisa dari CA publik seperti Let's Encrypt atau dari Cloudflare Origin CA. Cloudflare menyarankan Full atau Full (strict) jika memungkinkan.

Artinya nginx perlu mendengarkan di 443 dengan sertifikat. Cara memasangnya otomatis dibahas di perbandingan Caddy dan nginx untuk HTTPS otomatis. Kalau kamu tidak mau membuka port ke internet sama sekali, ada jalur lain lewat Cloudflare Tunnel. Di sini kita tetap memakai nginx dengan port terbuka.

# Cloudflare dashboard
#   DNS:       A  example.com  ->  203.0.113.10   (proxy on)
#   SSL/TLS:   Full (strict)

# /etc/nginx/conf.d/example.com.conf
server {
    listen 80;
    server_name example.com www.example.com;
    # Safe with Full (strict). With Flexible this line causes a redirect loop.
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name example.com www.example.com;

    # Placeholder paths: use the certificate you issued (public CA or Cloudflare Origin CA)
    ssl_certificate     /etc/ssl/example.com/fullchain.pem;
    ssl_certificate_key /etc/ssl/example.com/privkey.pem;

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

Mode Flexible plus redirect HTTP ke HTTPS di nginx adalah penyebab klasik ERR_TOO_MANY_REDIRECTS. Cloudflare mengirim HTTP, nginx menjawab redirect ke HTTPS, Cloudflare mengirim HTTP lagi, dan seterusnya. Pilih satu: Full (strict) dengan redirect, atau tanpa redirect di nginx.

Langkah 6: Pulihkan IP asli pengunjung

Di belakang Cloudflare, setiap koneksi ke nginx datang dari alamat Cloudflare, jadi log, rate limit, dan firewall hanya melihat proxy. Cloudflare menaruh IP asli pengunjung di header CF-Connecting-IP. Modul realip di nginx menukarnya: real_ip_header menyebut nama header, dan set_real_ip_from mendaftar alamat yang dipercaya untuk mengirimnya.

# 1. Is the realip module built in? (it is not on by default)
nginx -V 2>&1 | grep -o with-http_realip_module

# 2. /etc/nginx/conf.d/cloudflare-realip.conf (http context)
#    One line per range from https://www.cloudflare.com/ips/ , nothing else.
set_real_ip_from <range-1-from-cloudflare>;
set_real_ip_from <range-2-from-cloudflare>;
real_ip_header CF-Connecting-IP;

# Wrong: trusts the header from the whole internet, so anyone can fake their IP
# set_real_ip_from 0.0.0.0/0;

# 3. Reload, then watch the log
sudo nginx -t && sudo nginx -s reload
sudo tail -n 5 /var/log/nginx/access.log

Daftar tepercaya itu bagian keamanannya. Isi hanya dengan rentang yang dipublikasikan Cloudflare di halaman IP mereka, dan perbarui secara rutin, seperti yang disarankan dokumentasinya. Modul realip tidak ikut di-build secara default, jadi cek dulu apakah nginx-mu punya with-http_realip_module.

Output yang diharapkan: Output yang diharapkan: perintah pengecekan mencetak with-http_realip_module. Setelah reload, baris baru di access.log menampilkan IP pengunjung, bukan alamat dari rentang Cloudflare.

Error umum dan cara memperbaikinya

Hampir semua masalah reverse proxy jatuh ke sembilan gejala ini. Cari gejalamu di kolom pertama, lalu cek sebabnya sebelum mengubah apa pun.

GejalaPenyebabPerbaikan
502 Bad Gateway dengan halaman bertuliskan nginxnginx hidup tetapi tidak bisa menyambung ke app: container mati, belum siap, atau port di proxy_pass salahJalankan docker compose ps lalu curl -I http://127.0.0.1:3000. Samakan port di proxy_pass dengan angka sebelum titik dua pada ports. Lihat baris terakhir error.log untuk pesan upstream-nya.
502 atau 504 dengan halaman bermerek CloudflareMenurut dokumentasi Cloudflare, ini kasus yang paling umum: error datang dari origin, dan Cloudflare hanya meneruskannyaDebug seperti baris pertama: yang bermasalah nginx atau app di server, bukan Cloudflare. Halaman kosong tanpa merek Cloudflare adalah kasus lain, misalnya kompresi gzip yang rusak di origin.
504 Gateway Timeout setelah menunggu lamaApp butuh lebih dari 60 detik untuk menjawab; proxy_read_timeout bawaan nginx adalah 60sPerbaiki endpoint yang lambat lebih dulu. Kalau memang harus lama, naikkan proxy_read_timeout hanya di location itu, bukan di seluruh server.
ERR_TOO_MANY_REDIRECTS di browserMode SSL Cloudflare Flexible mengirim HTTP ke origin, lalu nginx me-redirect HTTP ke HTTPS, dan kedua sisi saling memantulkan requestPasang sertifikat di nginx dan ganti mode ke Full (strict). Atau, kalau tetap Flexible, hapus redirect HTTP ke HTTPS di nginx.
Error 521: web server is downCloudflare tidak bisa menyambung ke origin; biasanya nginx mati, atau port 80 dan 443 tertutup firewallDi server jalankan sudo systemctl status nginx dan curl -I http://127.0.0.1. Pastikan firewall membuka port 80 dan 443. Dokumentasi Cloudflare juga menyarankan memeriksa log proxy atau firewall di antaranya.
Yang muncul halaman Welcome to nginx!File konfigurasimu tidak dimuat, server_name tidak cocok dengan domain, atau kamu lupa reloadJalankan sudo nginx -T dan cari nama domainmu di outputnya. Kalau tidak ada, file-nya ada di folder yang tidak di-include. Setelah memperbaiki, sudo nginx -s reload.
Link atau redirect di app mengarah ke 127.0.0.1:3000Header Host tidak diteruskan, jadi app menerima nilai bawaan nginx, yaitu alamat upstream-nya sendiriTambahkan proxy_set_header Host $host; di location yang memakai proxy_pass, lalu reload.
Log nginx hanya berisi alamat IP CloudflareSemua koneksi datang dari Cloudflare, dan IP asli pengunjung ada di header CF-Connecting-IPAktifkan modul realip: set_real_ip_from untuk setiap rentang Cloudflare dan real_ip_header CF-Connecting-IP (Langkah 6).
404 di path yang di app jelas adaproxy_pass memakai URI (ada garis miring di akhir), sehingga nginx mengganti bagian path yang cocok dengan locationTulis proxy_pass tanpa garis miring dan tanpa path kalau path dari klien harus diteruskan apa adanya.

Kalau gejalamu tidak ada di tabel, debug dari dalam ke luar. Urutan ini memisahkan app, nginx, dan Cloudflare satu per satu, jadi kamu tahu lapisan mana yang salah.

# 1. Is the app alive on its own?
docker compose ps
curl -I http://127.0.0.1:3000

# 2. Is nginx healthy, and which config did it load?
sudo systemctl status nginx
sudo nginx -t

# 3. Does nginx reach the app? Read what it says about the upstream
curl -I -H "Host: example.com" http://127.0.0.1
sudo tail -n 20 /var/log/nginx/error.log

# 4. Only now look outside: DNS record, SSL mode, then the real URL
curl -I https://example.com

Kalau langkah 1 gagal, masalahnya di app atau container. Kalau langkah 1 lolos tetapi langkah 3 gagal, masalahnya di konfigurasi nginx. Kalau keduanya lolos tetapi domain tetap error, lihat pengaturan DNS dan mode SSL di Cloudflare.

Checklist sebelum kamu menyebut selesai

  • Port container hanya terikat ke 127.0.0.1 dan docker compose ps membuktikannya.
  • sudo nginx -t lulus sebelum setiap reload.
  • proxy_set_header Host $host dan X-Forwarded-Proto ada di location yang memakai proxy_pass.
  • Mode SSL Cloudflare adalah Full (strict) dan tidak ada redirect loop.
  • set_real_ip_from hanya berisi rentang Cloudflare, dan log menampilkan IP pengunjung.
  • Kamu tahu urutan debug: app, nginx, lalu Cloudflare.

Belajar lebih dalam

Panduan ini sengaja berupa resep. Kalau kamu ingin tahu kenapa Cloudflare menyimpan file ber-hash selama setahun tetapi tidak pernah menyimpan HTML, dan apa arti DYNAMIC pada header cf-cache-status, lanjutkan ke lesson gratis berikut:

Reverse Proxy Nginx dan Docker Compose di Balik Cloudflare

Lesson itu menjalankan nginx dan app di laptop dengan Docker Compose, jadi kamu bisa mengulang percobaannya tanpa menyentuh server produksi.

FAQ

Pertanyaan Umum

Jawaban singkat untuk pertanyaan yang paling sering diajukan pembaca tentang topik ini.

Apa itu nginx reverse proxy dan kenapa perlu untuk Docker?

Reverse proxy menerima setiap request lebih dulu lalu meneruskannya ke aplikasimu, dan di nginx itu dilakukan oleh directive proxy_pass. Dengan Docker, container cukup mendengarkan di 127.0.0.1 sementara nginx mengurus domain dan HTTPS. Jadi tidak ada yang bisa menjangkau container tanpa lewat nginx.

Kenapa nginx reverse proxy saya error 502 Bad Gateway?

nginx hidup tetapi tidak bisa menyambung ke app di balik proxy_pass. Penyebab umumnya container mati, port di proxy_pass salah, atau app belum siap. Jalankan curl -I http://127.0.0.1:3000 di server dan baca ujung error.log untuk tahu yang mana.

Kenapa muncul ERR_TOO_MANY_REDIRECTS setelah memasang Cloudflare di depan nginx?

Biasanya Cloudflare berada di mode Flexible, jadi ia menyambung ke origin lewat HTTP sementara nginx me-redirect HTTP ke HTTPS. Request pun memantul tanpa henti. Pasang sertifikat di nginx lalu ganti ke Full (strict), atau hapus redirect di nginx kalau tetap memakai Flexible.

Apakah proxy_set_header Host wajib di nginx?

Untuk kebanyakan aplikasi, ya. Secara default nginx mengirim Host berisi alamat upstream-nya sendiri, misalnya 127.0.0.1:3000, sehingga app membuat link dan redirect ke tempat yang salah. proxy_set_header Host $host meneruskan domain yang benar-benar diminta pengunjung.

Bagaimana cara melihat IP asli pengunjung di nginx yang di belakang Cloudflare?

Pakai modul realip dengan real_ip_header CF-Connecting-IP dan satu baris set_real_ip_from untuk setiap rentang yang dipublikasikan Cloudflare. Percayai hanya rentang itu, karena header yang dipercaya dari alamat mana pun membuat siapa saja bisa memilih IP-nya. Modul ini tidak ikut di-build secara default, jadi cek nginx -V untuk with-http_realip_module.