Soms werk ik aan projecten waarbij een externe partij mijn lokale omgeving moet bereiken. Denk aan een betaalprovider die een webhook naar mijn machine stuurt, of een klant die live wil meekijken terwijl ik iets instel. Expose.dev en ngrok zijn dan de voor de hand liggende keuzes, maar ik loop daar steeds tegen hetzelfde punt aan: willekeurige subdomeinen die veranderen zodra je de tunnel herstart, of een betaalmuur voor iets dat eigenlijk basisinfrastructuur zou moeten zijn. Cloudflare Tunnel biedt een andere aanpak, en na het configureren ervan ben ik er eigenlijk niet meer van losgeraakt.
Wat Cloudflare Tunnel anders doet
Cloudflare Tunnel werkt via een daemon genaamd cloudflared die een uitgaande verbinding opent naar het Cloudflare-netwerk. Er hoeft geen enkel inkomend poortnummer open te staan op jouw router of firewall, want het verkeer loopt altijd van binnenuit naar buiten. Dat is een fundamenteel ander model dan tools die een publiek IP van een externe server koppelen aan jouw lokale poort.
Omdat je een eigen domein koppelt, krijg je een vast subdomein dat niet verandert bij het herstarten van de daemon. Dit maakt het heel bruikbaar voor situaties waarbij je een webhook-URL bij een externe dienst configureert, want je hoeft die URL niet bij te werken zodra je opnieuw opstart. Voor platforms als Stripe, Mollie of GitHub Actions is dat een groot praktisch voordeel.
Het enige dat je nodig hebt is een domein dat via Cloudflare's nameservers loopt. Als je dat al doet voor productiedomeinen, kost het instellen van een tunnel vrijwel niets extra. Heb je dat nog niet, dan is het wel een extra stap. Voor mij was dat geen drempel, want het meeste DNS-beheer gaat al via Cloudflare.
Installeren en configureren op macOS
De installatie van cloudflared op macOS gaat via Homebrew.
brew install cloudflared
Daarna log je in met je Cloudflare-account, zodat de tool weet welk domein het mag aansturen.
cloudflared tunnel login
Dit opent een browser waar je een domein selecteert en autoriseert. Na het inloggen maak je een tunnel aan met een naam naar keuze.
cloudflared tunnel create mijn-dev-tunnel
Dit genereert een tunnel-ID en een credentials-bestand in ~/.cloudflared/. Noteer het tunnel-ID, want je hebt het straks nodig in de configuratie. Daarna stel je in welk subdomein naar welke lokale poort moet routeren. Dit doe je in een YAML-bestand.
tunnel: jouw-tunnel-id-hier
credentials-file: /Users/pim/.cloudflared/jouw-tunnel-id-hier.json
ingress:
- hostname: dev.jouwdomein.nl
service: http://localhost:8000
- service: http_status:404
De laatste regel in het ingress-blok is verplicht: Cloudflare wil altijd een catch-all route hebben voor verkeer dat niet overeenkomt met een hostname. Sla dit op als ~/.cloudflared/config.yml. Vervolgens koppel je het subdomein aan de tunnel, zodat de DNS-record automatisch wordt aangemaakt in Cloudflare.
cloudflared tunnel route dns mijn-dev-tunnel dev.jouwdomein.nl
Nu kan je de tunnel starten met het commando hieronder, en je lokale server op poort 8000 is bereikbaar via https://dev.jouwdomein.nl.
cloudflared tunnel run mijn-dev-tunnel
De tunnel als achtergrondservice draaien
Handmatig de tunnel starten en stoppen is prima voor een incidentele sessie, maar ik wil dat hij vanzelf meestuurt zodra mijn machine opstart. Op macOS regel ik dat als een launchd-service.
sudo cloudflared service install
Dit registreert de tunnel als een systeemservice op basis van je bestaande config.yml. Na een reboot is de tunnel beschikbaar zonder dat je eraan hoeft te denken. Je kunt de status controleren via sudo launchctl list | grep cloudflared of door simpelweg je subdomein te bezoeken.
Op Linux werkt het via systemd, en het commando is identiek. De service-definitie wordt dan geplaatst in /etc/systemd/system/ en je kan hem beheren met de gebruikelijke systemctl-commando's.
sudo systemctl status cloudflared
Een voordeel dat ik hierbij merk: omdat de tunnel zonder handmatige actie actief is, kan ik een webhook in een externe dienst eenmalig instellen en hoef ik die URL maanden later niet opnieuw te configureren. Voor projecten waarbij ik een webhook van een CRM of betaalplatform debug, scheelt dit veel heen-en-weer.
Meerdere services op één tunnel routeren
Eén tunnel kan meerdere hostnames afhandelen. Dit is handig als je gelijktijdig aan een frontend en een API werkt die allebei bereikbaar moeten zijn.
tunnel: jouw-tunnel-id-hier
credentials-file: /Users/pim/.cloudflared/jouw-tunnel-id-hier.json
ingress:
- hostname: api.jouwdomein.nl
service: http://localhost:8000
- hostname: app.jouwdomein.nl
service: http://localhost:3000
- service: http_status:404
Voor elk nieuw hostname voeg je een aparte DNS-route toe via cloudflared tunnel route dns. De tunnel zelf hoeft niet opnieuw te worden aangemaakt. Dit werkt goed als je een Laravel API lokaal op poort 8000 draait en een Node.js frontend op poort 3000, want beide zijn dan bereikbaar op hun eigen subdomein via dezelfde actieve tunnel.
Het is wel goed om te weten dat Cloudflare Tunnel HTTPS afhandelt op de edge. Het verkeer tussen de cloudflared-daemon en jouw lokale service loopt onversleuteld over localhost, maar wat de buitenwereld ziet is altijd HTTPS. Je hoeft dus geen lokaal SSL-certificaat te configureren voor de tunnel zelf. Dat is anders dan bij expose.dev of Laravel Herd, waar je lokale HTTPS apart instelt.
Wanneer ik toch voor expose.dev kies
Cloudflare Tunnel heeft één praktisch nadeel: je hebt er een eigen domein voor nodig dat via Cloudflare's nameservers loopt. Bij korte demo's voor klanten die ik snel wil opzetten zonder iets te configureren, pak ik soms nog expose.dev. Daar start ik een tunnel op met één commando en krijg ik direct een tijdelijk publiek adres.
expose share http://localhost:8000
De drempel is nul, want er is geen account of DNS-configuratie nodig voor de basisversie. Maar zodra ik langere tijd met een externe dienst werk, of een stabiele URL nodig heb die ik ook morgen nog kan bezoeken, ga ik terug naar Cloudflare Tunnel.
De keuze hangt dus samen met de levensduur van wat je bouwt. Voor een snelle test van vijf minuten is expose.dev of zelfs npx localtunnel sneller. Voor structureel lokale development waarbij externe diensten jou moeten kunnen bereiken, betaalt de eenmalige configuratie van Cloudflare Tunnel zichzelf terug binnen de eerste dag.
Wat ik zelf doe is de tunnel permanent draaien op mijn werkcomputer en alleen de ingress-regels aanpassen wanneer een project een andere poort nodig heeft. Dat kost letterlijk dertig seconden: de YAML aanpassen, cloudflared herstarten, klaar. De stabiliteit en het feit dat er niks achter een betaalmuur zit, maakt het voor mij de standaardkeuze geworden.