Skip to main content
BlogSecurity

Ekspos Layanan Self-Hosted dengan Aman via Cloudflare Tunnel

Cara saya mengakses homelab dan VPS dari mana saja tanpa port masuk terbuka — konfigurasi cloudflared, ingress rules, dan kebijakan Zero Trust.

Matthews Wong

Software Engineer

Juli 20269 menit baca
Ekspos Layanan Self-Hosted dengan Aman via Cloudflare Tunnel

Foto oleh Chi-Hung Lin via Wikimedia Commons (CC BY-SA 3.0)

Ringkasan Utama

Cloudflare Tunnel menjalankan daemon kecil bernama cloudflared yang menghubungi Cloudflare secara keluar, jadi Anda mengekspos layanan homelab atau VPS tanpa satu pun port masuk terbuka. Anda memetakan hostname ke layanan lokal di konfigurasi ingress, lalu menempatkan kebijakan Zero Trust Access di depan untuk mewajibkan login berbasis identitas sebelum siapa pun mencapai aplikasi.

Selama bertahun-tahun jawaban standar untuk mengakses server rumah adalah port forwarding: membuka lubang di router, mengekspos port 443, dan berharap konfigurasi reverse proxy serta TLS Anda tidak pernah bermasalah. Di koneksi rumahan Anda sering tidak bisa melakukannya sama sekali, karena ISP berada di belakang carrier-grade NAT dan tidak pernah memberi Anda IP publik yang bisa dirutekan. Di VPS Anda bisa membuka port, tetapi setiap port terbuka adalah layar login yang bisa digempur oleh seluruh internet.

Cloudflare Tunnel membalik arahnya. Alih-alih internet menghubungi Anda, sebuah agen ringan menghubungi edge Cloudflare secara keluar dan menjaga koneksi persisten tetap terbuka. Trafik untuk hostname Anda tiba di Cloudflare, dikirim melalui tunnel keluar yang sudah ada itu, dan baru kemudian mencapai layanan Anda. Firewall Anda tetap tertutup penuh di sisi masuk. Saya menjalankan ini di depan beberapa dashboard internal dan sebuah layanan di VPS saya, dan diam-diam ini menggantikan setiap setup reverse-proxy-plus-open-port yang dulu harus saya awasi.

Cara tunnel bekerja tanpa port terbuka

Daemon-nya, cloudflared, membangun beberapa koneksi keluar ke pusat data Cloudflare terdekat melalui QUIC (UDP 7844) atau HTTPS. Karena ini koneksi keluar yang sudah diizinkan NAT dan firewall Anda, tidak ada yang perlu dibuka. Ketika permintaan mengenai hostname Anda, Cloudflare merutekannya ke dalam tunnel dan cloudflared mem-proxy-kannya ke alamat lokal seperti localhost pada suatu port. Layanan origin tidak pernah melihat internet publik secara langsung, dan port-nya tidak pernah terikat ke antarmuka publik.

  • Tanpa aturan firewall masuk — tunnel bersifat keluar saja, jadi ia bekerja di belakang CGNAT maupun firewall VPS yang terkunci.
  • TLS diterminasi di edge Cloudflare, jadi Anda tidak mengelola sertifikat di origin untuk hostname publik.
  • Filter DDoS dan bot terjadi di edge sebelum trafik masuk ke tunnel Anda.
  • Alamat IP origin Anda tidak pernah terekspos di DNS — record menunjuk ke Cloudflare, bukan ke Anda.

Memasang cloudflared dan membuat tunnel

Jalur klasik menggunakan CLI untuk autentikasi, membuat tunnel bernama, dan menghasilkan file kredensial. Autentikasi membuka browser sehingga Anda bisa memilih zona mana yang dimiliki tunnel. Membuat tunnel menulis file kredensial JSON yang namanya adalah UUID tunnel — perlakukan file itu seperti kata sandi.

# Install on Debian/Ubuntu (official Cloudflare repo)
sudo mkdir -p /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg \
  | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] \
https://pkg.cloudflare.com/cloudflared any main" \
  | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt-get update && sudo apt-get install -y cloudflared

# Authenticate (opens a browser to pick your zone)
cloudflared tunnel login

# Create a named tunnel -> writes ~/.cloudflared/<UUID>.json
cloudflared tunnel create homelab

# Route a DNS hostname to this tunnel
cloudflared tunnel route dns homelab grafana.example.com

Lebih baik gunakan metode berbasis token untuk kontainer dan setup cepat. Di dashboard bagian Zero Trust, buat tunnel dan ia memberi Anda satu token konektor — tanpa file kredensial yang harus dipindah-pindah, dan ingress rules bisa diedit di UI. Metode config-file CLI di bawah lebih baik jika Anda ingin aturan routing berada di version control.

File config.yml dan ingress rules

Ketika Anda mengelola tunnel secara lokal, sebuah file konfigurasi memetakan setiap hostname publik ke layanan lokal. Cloudflare mengevaluasi ingress rules dari atas ke bawah dan berhenti pada kecocokan pertama, jadi urutan penting: taruh hostname spesifik lebih dulu dan wildcard luas terakhir. Aturan yang benar-benar sering menjebak orang adalah yang terakhir.

# ~/.cloudflared/config.yml
tunnel: 6ff42ae2-765d-4adf-8112-31c55c1551ef
credentials-file: /home/matthews/.cloudflared/6ff42ae2-...json

ingress:
  # Specific hostnames first
  - hostname: grafana.example.com
    service: http://localhost:3000
  - hostname: uptime.example.com
    service: http://localhost:3001
  # A whole app behind one subdomain tree
  - hostname: "*.lab.example.com"
    service: http://localhost:8080
  # REQUIRED catch-all — must be last, no hostname
  - service: http_status:404

Aturan ingress terakhir harus berupa catch-all tanpa hostname, misalnya mengembalikan status HTTP 404. Tanpa itu cloudflared menolak untuk memulai dan mencetak error validasi. Ini menjebak hampir semua orang pada konfigurasi pertama mereka — jika daemon tidak mau boot, periksa apakah aturan terakhir tidak punya kunci hostname.

Setelah konfigurasi tervalidasi, jalankan sebagai service agar bertahan saat reboot. Di host systemd, cloudflared memasang unit service untuk Anda; di Docker, Anda mengarahkan kontainer ke routing yang sama tetapi memberikan token konektor alih-alih config yang di-mount.

Menjalankan cloudflared di Docker

Di VPS saya semuanya Docker Compose, jadi tunnel hanyalah satu service tambahan. Metode token paling bersih di sini — token konektor membawa identitas tunnel dan konfigurasi routing-nya, jadi kontainer tidak perlu apa pun yang di-mount. Simpan token di file environment, jangan pernah di-hardcode di file compose yang Anda commit.

# docker-compose.yml
services:
  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared
    restart: unless-stopped
    command: tunnel run
    environment:
      - TUNNEL_TOKEN=${TUNNEL_TOKEN}
    # No ports published — that is the whole point.
    networks:
      - web

networks:
  web:
    external: true

# .env  (git-ignored)
# TUNNEL_TOKEN=eyJhIjoiTOKEN...

Taruh cloudflared di jaringan Docker yang sama dengan aplikasi yang di-proxy-nya, lalu arahkan ingress ke nama kontainer alih-alih localhost — misalnya http://grafana:3000. Kini tidak ada kontainer aplikasi Anda yang perlu port terpublikasi juga; tunnel menjangkaunya lewat jaringan Docker internal dan host tidak punya apa pun yang mendengarkan secara publik.

Menambahkan Zero Trust Access di depan

Tunnel saja tetap mempublikasikan layanan Anda ke siapa pun yang tahu URL-nya. Untuk apa pun yang tidak dimaksudkan sepenuhnya publik, lapisi dengan Cloudflare Access di atasnya. Access berada di edge dan memaksa setiap permintaan melewati pemeriksaan identitas sebelum pernah dirutekan ke tunnel Anda. Semua aplikasi Access bersifat deny by default — pengguna harus mencocokkan kebijakan Allow terlebih dulu — jadi orang asing tanpa autentikasi tidak pernah mencapai origin Anda sama sekali.

  1. Di dashboard, buka Zero Trust, lalu Access, lalu Applications, dan buat aplikasi Self-hosted baru.
  2. Atur domain aplikasi ke hostname persis yang dilayani tunnel Anda, misalnya grafana.example.com.
  3. Tambahkan kebijakan Allow — yang paling sederhana adalah aturan yang mencocokkan alamat email Anda sendiri, atau email yang berakhir dengan domain Anda.
  4. Hubungkan penyedia identitas seperti Google atau GitHub, atau gunakan one-time PIN bawaan yang dikirim ke email yang diizinkan.
  5. Simpan. Mengunjungi hostname kini mengarahkan ke halaman login, dan hanya identitas yang cocok yang lolos ke aplikasi.

Di luar allow-list email sederhana, kebijakan Access dapat membatasi berdasarkan keanggotaan grup IdP, negara, postur perangkat, dan durasi sesi. Untuk layanan yang harus menerima panggilan programatik, tambahkan service token agar skrip bisa berautentikasi dengan sepasang header alih-alih login browser. Model mentalnya sederhana: tunnel menentukan bagaimana trafik mencapai mesin Anda, dan Access menentukan siapa yang boleh mengirimnya.

Apa yang layak diekspos begini — dan apa yang tidak

LayananPendekatanAlasan
Dashboard internal (Grafana, uptime, Portainer)Tunnel plus allow-list AccessBersifat privat; gerbang identitas menjaganya tetap milik Anda.
Blog publik atau situs marketingTunnel saja, tanpa AccessDitujukan untuk semua orang; Access akan memblokir pengunjung asli.
SSH ke hostTunnel plus Access dengan sesi pendekMenjangkau SSH tanpa port 22 terbuka ke dunia.
Port database (Postgres, Redis)Rute jaringan privat, bukan hostname publikJangan pernah mengekspos DB sebagai aplikasi HTTP; gunakan WARP atau routing privat.
Apa pun yang menangani pembayaran atau PIIAccess plus IdP ketat dan postur perangkatAuth di edge adalah kontrol, bukan pengganti keamanan tingkat aplikasi.

Trade-off yang layak disebut: Anda merutekan trafik melalui Cloudflare dan mempercayai edge mereka untuk menterminasi TLS. Untuk homelab dan sebagian besar proyek sampingan itu kesepakatan yang adil demi kenyamanan dan firewall yang tertutup. Jika Anda tidak bisa menerima pihak ketiga menterminasi TLS, alternatif self-hosted seperti VPN WireGuard atau reverse SSH tunnel biasa menjaga semuanya di tangan Anda dengan biaya setup lebih banyak dan tanpa filter edge. Bagi saya, di jalur rumahan di belakang CGNAT dan VPS yang ingin saya kunci, Cloudflare Tunnel plus Access adalah setup yang benar-benar terus saya jalankan.

FAQ

Pertanyaan Umum

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

Apakah saya perlu membuka port untuk menggunakan Cloudflare Tunnel?

Tidak. Daemon cloudflared hanya membuat koneksi keluar ke edge Cloudflare, jadi router dan firewall Anda tetap tertutup penuh di sisi masuk. Inilah alasan ia bekerja di belakang carrier-grade NAT di mana port forwarding tidak mungkin, dan mengapa aman menjalankannya di VPS yang diperketat tanpa port publik yang terikat.

Apa perbedaan antara Cloudflare Tunnel dan Zero Trust Access?

Tunnel menentukan bagaimana trafik mencapai layanan Anda — ia adalah transport yang membawa permintaan dari edge Cloudflare ke origin Anda tanpa port terbuka. Access menentukan siapa yang boleh lewat, menambahkan pemeriksaan login berbasis identitas di edge sebelum permintaan apa pun dirutekan ke tunnel. Anda sering memakai keduanya bersama: tunnel untuk konektivitas, Access untuk autentikasi.

Mengapa cloudflared menolak memulai dengan error konfigurasi ingress?

Penyebab paling umum adalah aturan catch-all yang hilang. Daftar ingress Anda harus diakhiri dengan aturan yang tidak punya hostname, misalnya yang mengembalikan status HTTP 404. Cloudflare mengevaluasi aturan dari atas ke bawah dan mewajibkan fallback terakhir itu, jadi jika daemon tidak mau boot, pastikan entri terakhir tidak punya kunci hostname.

Bisakah saya menjalankan Cloudflare Tunnel di Docker tanpa file kredensial?

Bisa. Buat tunnel di dashboard Zero Trust dan ia memberi Anda token konektor yang sudah berisi identitas tunnel dan konfigurasi routing. Berikan token itu ke image resmi cloudflare/cloudflared melalui variabel environment TUNNEL_TOKEN dan jalankan perintah tunnel — tidak ada file konfigurasi atau JSON kredensial yang perlu di-mount.

Apakah aman mengekspos database seperti PostgreSQL melalui tunnel?

Tidak sebagai aplikasi HTTP publik. Hostname publik Cloudflare Tunnel dibuat untuk layanan HTTP, dan database tidak boleh dipublikasikan seperti itu. Untuk akses database gunakan routing jaringan privat Cloudflare dengan klien WARP, atau simpan database di jaringan internal yang dijangkau tunnel hanya untuk kontainer aplikasi, jangan pernah sebagai rute publik.