Publishing a Next.js app through a Cloudflare Tunnel
Serving a self-hosted app on a real domain without opening a single inbound port — and the resolver quirk that made it look broken when it was not.
Addresses in this guide are placeholders in 10.0.0.0/24 and the domain is example.dev. Everything else is verbatim from the working notes.
- 01
Install and authenticate
The tunnel dials out to Cloudflare and traffic returns down that connection, so the origin needs no port forwarding and no public address at all.
wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb sudo dpkg -i cloudflared-linux-amd64.deb cloudflared login - 02
Create the tunnel and point it at the app
cloudflared tunnel create nextjs-tunnel/etc/cloudflared/config.yml tunnel: nextjs-tunnel credentials-file: /home/master/.cloudflared/<tunnel-id>.json ingress: - hostname: example.dev service: http://10.0.0.80:30000 - service: http_status:404 - 03
Route DNS and run it as a service
Delete any existing A/AAAA/CNAME record for the hostname first; the route command creates the CNAME it needs and will not overwrite a conflicting record.
cloudflared tunnel route dns nextjs-tunnel example.dev sudo cloudflared service install sudo systemctl restart cloudflared - 04
The resolver fix
On the origin host the tunnel hostname resolved to a local IPv6 address, so requests from the machine itself never left the box — while every external client worked fine. Pinning the resolver fixed it.
/etc/systemd/resolved.conf [Resolve] DNS=1.1.1.1 FallbackDNS=8.8.8.8sudo systemctl restart systemd-resolved curl -v https://example.dev