← Semua catatan

Kenapa server tanpa IP publik tetap bisa punya domain sendiri

·4 menit baca English

Saya punya mesin kecil di cloud. Empat vCPU, 8 GB, Debian, nginx menyala di port 8080. Isinya sebuah situs statis yang sudah jalan sempurna kalau dibuka lewat alamat yang diberikan penyedianya.

Lalu saya punya domain sendiri yang menganggur. Pertanyaannya kelihatan sepele: bagaimana menyambungkan keduanya?

Jawaban naifnya: arahkan domain ke IP mesin itu. Yang saya temukan kemudian adalah tiga dinding.

Tiga hal yang membuat jalan lurus itu buntu

Pertama, tidak ada alamat untuk dituju. Mesinnya keluar lewat satu pintu bersama, dan tidak ada pintu masuk dari luar. Alamat IP yang terlihat di log hanyalah alamat pintu keluarnya. Tidak ada yang bisa ditulis di record DNS.

Kedua, sertifikat HTTPS-nya bukan untuk domain saya. Sisi masuk penyedia memegang sertifikat untuk domain mereka sendiri. Kalau saya menunjuk domain saya ke sana, browser akan menolak koneksi karena nama di sertifikat tidak cocok. Ini tidak bisa diakali dengan CNAME.

Ketiga, dan ini yang paling tidak terduga: header cache saya ditimpa. nginx di dalam mesin mengirim Cache-Control dengan benar, tapi sisi masuk penyedia menggantinya menjadi no-store — artinya jangan simpan apa pun. Setiap kunjungan mengunduh ulang seluruh halaman, dan setiap permintaan memakan sekitar 850 ms hanya untuk sampai ke mesin.

Dinding ketiga itu yang paling mengubah cara saya berpikir. Bukan soal "bagaimana menyambungkan domain", tapi "bagaimana membuat sesuatu di depan mesin ini yang bisa menyimpan jawaban".

Jawaban yang terlihat paling canggih justru salah

Pilihan pertama saya adalah Cloudflare Tunnel. Kedengarannya sempurna: pasang satu program di dalam mesin, buka terowongan ke Cloudflare, domain saya langsung hidup tanpa perlu IP publik.

Saya pasang, dan memang berjalan. Sampai saya sadar satu hal.

Terowongan itu adalah proses yang harus hidup terus. Mesin saya di-pause otomatis setelah 30 menit tanpa aktivitas. Begitu mesin tidur, terowongan putus, dan Cloudflare membalas 502. Yang lebih fatal: permintaan lewat terowongan tidak pernah menyentuh sisi masuk penyedia, jadi tidak ada satu pun bagian sistem yang bisa membangunkan mesin itu kembali.

Solusinya bentrok langsung dengan cara kerja platformnya. Bukan konfigurasi yang kurang, tapi konsep yang salah sejak awal.

Pelajaran yang saya ambil: solusi paling canggih belum tentu cocok. Yang menentukan bukan kecanggihannya, tapi apakah dia sejalan dengan batasan yang ada.

Yang akhirnya dipakai: satu program kecil di edge

Cloudflare Worker adalah program yang berjalan di jaringan Cloudflare, bukan di mesin saya. Dia bisa menerima permintaan, memutuskan harus berbuat apa, dan menyimpan salinan jawaban.

Bentuk intinya sesederhana ini:

const res = await fetch(ORIGIN + url.pathname);

Tapi tiga detail yang membuatnya bekerja:

  • Permintaan ke URL publik penyedia justru membangunkan mesin. Jadi Worker tidak hanya meneruskan; dia yang menekan tombol bangun.
  • Worker menyimpan salinannya sendiri lewat Cache API, di luar kendali header no-store yang dipaksakan tadi.
  • Kalau mesin mati, salinan lama tetap disajikan. Pengunjung melihat halaman, bukan error.

Hasilnya: permintaan pertama butuh 1.944 ms karena mesin dibangunkan. Permintaan berikutnya 1 ms, dan mesin boleh tetap tidur.

Dua hal yang baru ketahuan setelah situsnya hidup

Semua uji lokal saya lulus. Lalu situsnya benar-benar hidup, dan dua masalah muncul yang tidak terlihat di uji mana pun.

Situs saya dilarang diindeks Google. Sisi masuk penyedia menempelkan X-Robots-Tag: noindex, nofollow ke setiap respons. Bukan dari nginx saya, bukan dari Cloudflare. Jadi meski robots.txt mengizinkan, Google tetap dilarang. Perbaikannya: Worker membuang header itu.

Cloudflare menyimpan cache sendiri dan mengunci 4 jam. Header max-age yang saya kirim ditimpa menjadi 14.400 detik. Lebih buruk, salinan itu ada di cache edge Cloudflare, dan parameter ?fresh=1 tidak bisa membatalkannya karena URL-nya dianggap berbeda. Perbaikannya: Worker mengirim no-store ke klien, sehingga satu-satunya cache adalah milik Worker sendiri, yang saya kendalikan penuh.

Dua-duanya hanya ketahuan karena saya memeriksa header dari domain sungguhan, bukan dari localhost.

Angka akhirnya

MetrikSebelumSesudah
Waktu ke byte pertama1.944 ms70–82 ms
First Contentful Paint1.924–2.832 ms108–124 ms
Boleh diindekstidakya
Biaya—nol

Biayanya nol karena paket gratis Cloudflare memberi 100.000 permintaan per hari, dan paket gratis penyedia memberi 100 GiB egress per bulan. Untuk situs sebesar ini, dua-duanya berlebih.

Yang saya pelajari

Batasan platform bukan halangan yang harus dilawan. Dia bentuk masalah yang harus dibaca ulang.

Selama saya menganggap pertanyaannya "bagaimana menyambungkan domain ke mesin", semua jalan buntu. Begitu pertanyaannya berubah menjadi "bagaimana menaruh sesuatu di depan mesin yang bisa menyimpan jawaban", solusinya justru sederhana — dan kebetulan menyelesaikan masalah kecepatan sekaligus.

Satu hal lagi: verifikasi di lapangan tidak bisa digantikan. Dua bug terakhir tidak akan pernah muncul di uji lokal, karena keduanya diciptakan oleh lapisan yang tidak ada di lokal.