Self-hosting over a Cloudflare Tunnel

Your machine is the server.
Cloudflare is the front door.

This page is not on a hosting platform. It is a plain Node process bound to 127.0.0.1:19000 on one machine, reached through an outbound-only tunnel that publishes it as chokita.com. No public IP. No port forwarding. No router surgery. No monthly bill.

Serving on:19000
Host—
Requests served—
This visit arrived via—

The only arrow that leaves your network starts inside it. That is the whole trick — an outbound connection is something every firewall already allows.

01

The pattern, in four moves

Any stack works — Node, Python, Go, PHP, a container, a dev server. If it listens on a local port and speaks HTTP, it can be published like this.

  1. 1

    Run the app on a local port

    Bind it to loopback or 0.0.0.0 on the machine that will host it. Port 19000 in this case. It never needs a firewall rule — nothing external talks to it directly.

    node server.js
    # [chokita] listening on http://0.0.0.0:19000
  2. 2

    Authenticate once, create a named tunnel

    cloudflared links to your Cloudflare account and mints a tunnel that belongs to it. A named tunnel keeps its identity forever — unlike a quick tunnel, its URL does not change every restart.

    cloudflared tunnel login
    cloudflared tunnel create chokita
    # Created tunnel chokita with id 6f1a…-…  (credentials file written locally)
  3. 3

    Point DNS at the tunnel

    One command creates the proxied DNS record. Traffic for the hostname now resolves to Cloudflare's edge, which knows to hand it to your connector.

    cloudflared tunnel route dns chokita chokita.com
  4. 4

    Connect the tunnel to your port, then keep it alive

    Ingress rules map the hostname to the local service. Run it as a service so it survives reboots and reconnects itself.

    # ~/.cloudflared/config.yml
    tunnel: chokita
    credentials-file: /home/you/.cloudflared/6f1a….json
    ingress:
      - hostname: chokita.com
        service: http://127.0.0.1:19000
      - service: http_status:404
02

Why people do this

The economics changed. A tunnel replaces most of what you used to rent a host for.

No inbound ports, ever

Nothing is exposed on your router. The connector makes an outbound TLS connection; the edge multiplexes requests down it. Port scans find nothing.

Works behind CGNAT

Mobile, fibre, a cafe, a VPS with a locked-down firewall — if the box can reach the internet, it can serve. No public IPv4 needed.

TLS handled at the edge

A real certificate for your domain, issued and renewed automatically. Your app deals in plain HTTP on loopback.

Edge protection for free

DDoS absorption, WAF rules, rate limiting, bot checks, caching and access policies sit in front of your box before a request reaches it.

Your data stays yours

Files, database, logs and secrets never leave the machine you control. The edge sees requests, not your disk.

Cost: the hardware you own

An old desktop, a mini PC, a Raspberry Pi. The tunnel itself is free. The recurring bill becomes electricity.

03

Copy this, end to end

Two ways in. Token mode is the one to automate — it is a single secret string and needs no credentials file on disk. Local mode is nicer while you are learning on your own machine.

Get a connector token from the Cloudflare dashboard (Zero Trust → Networks → Tunnels → your tunnel → Configure → Install connector). Treat it like a password: environment variable or secret store, never committed.

# 1. run the app on 19000
node server.js &

# 2. hand cloudflared the token — no credentials file required
export TUNNEL_TOKEN="eyJhIjoi…"          # PowerShell: $env:TUNNEL_TOKEN="eyJhIjoi…"
cloudflared tunnel run --token "$TUNNEL_TOKEN"

Why token mode wins: the token encodes the tunnel identity and the ingress rules. Rotate it in the dashboard and every connector picks up the change. It is also the shape that scales — the same token on a second machine gives you a second replica.

Credentials live in a JSON file next to config.yml. Good for a single always-on box you administer by hand.

cloudflared tunnel login
cloudflared tunnel create chokita
cloudflared tunnel route dns chokita chokita.com
cloudflared tunnel run chokita     # reads ~/.cloudflared/config.yml
tunnel: chokita
credentials-file: C:\Users\you\.cloudflared\6f1a….json
originRequest:
  connectTimeout: 30s
  noTLSVerify: false
ingress:
  - hostname: chokita.com
    service: http://127.0.0.1:19000
  - hostname: www.chokita.com
    service: http://127.0.0.1:19000
  - service: http_status:404

No account, no DNS, no config. You get a random *.trycloudflare.com URL that dies with the process. Perfect for showing someone your laptop's dev server for ten minutes. Not for anything you intend to keep.

cloudflared tunnel --url http://localhost:19000
# https://random-words-1234.trycloudflare.com  ← changes on every restart

Never put real user data behind a quick tunnel — the hostname is unguessable but public, and it is gone the moment you close the terminal.

Keep it running after you close the lid

A tunnel is only as available as the process and the machine behind it.

Linux (systemd)

sudo cloudflared service install "$TUNNEL_TOKEN"
sudo systemctl enable --now cloudflared
journalctl -u cloudflared -f

Windows

# run once as Administrator
cloudflared.exe service install $env:TUNNEL_TOKEN
Get-Service cloudflared
# keep the machine awake while serving:
powercfg /change standby-timeout-ac 0

Sanity check from anywhere

The same JSON this page is reading — proof the request made the full round trip.

curl -s https://chokita.com/api/status | jq '{ok, port, this_request_via_cloudflare, this_request_edge_ip}'
04

Is it actually up?

This panel polls the process on port 19000 every three seconds and prints what it reports about itself. No mock data — if the tunnel or the server dies, this goes red.

Connecting…
—
Service
—
Port
—
Bind
—
Hostname
—
Platform
—
Node
—
PID
—
Uptime
—
Requests served
—
Started
—
Last request via edge
—
Last request local
—

— raw JSON ↗ /healthz ↗

05

What you are signing up for

The honest version. This pattern is excellent, not magic.