Cloudflare Tunnel
Expose a self-hosted venelx-backend (127.0.0.1:4000) as https://api.venelx.com —
outbound-only, no port forwarding, no static IP. The dashboard stays on Vercel at
app.venelx.com.
Only api.venelx.com goes through the tunnel. app.venelx.com (dashboard) and
docs.venelx.com (this site) live on Vercel and need no tunnel configuration.
Concepts
| Field | Value |
|---|---|
| Tunnel name | venelx-api (any name works) |
| Tunnel ID | <tunnel-id> — a UUID assigned by Cloudflare at creation |
| Credentials file | ~/.cloudflared/<tunnel-id>.json (per machine — never commit it) |
| DNS | CNAME api → <tunnel-id>.cfargotunnel.com (proxied) |
| Ingress | api.venelx.com → http://127.0.0.1:4000 |
Setup
One-time login (stores a cert in ~/.cloudflared), then the repo script creates the
tunnel, writes the config, and routes DNS:
cloudflared tunnel login
cd <repo-root>
chmod +x scripts/setup-tunnel.sh
./scripts/setup-tunnel.sh # create tunnel + DNS + config if missing
./scripts/setup-tunnel.sh --recreate # delete the existing tunnel and start fresh
Then start the connector:
cloudflared tunnel run venelx-api
# production (launchd):
sudo launchctl kickstart -k system/com.venelx.app.cloudflared
./setup.sh calls setup-tunnel.sh automatically during a fresh install. To use a
different hostname: VENELX_TUNNEL_HOST=api2.example.com VENELX_TUNNEL_NAME=venelx-api-2 ./scripts/setup-tunnel.sh.
Config file
infra/cloudflare-tunnel.venelx.yml is copied to ~/.cloudflared/config.yml:
tunnel: <tunnel-id>
credentials-file: ~/.cloudflared/<tunnel-id>.json
ingress:
- hostname: api.venelx.com
service: http://127.0.0.1:4000
- service: http_status:404
The LaunchDaemon runs cloudflared tunnel run <tunnel-name> (see
infra/launchd/com.venelx.app.cloudflared.plist).
DNS
Automated (recommended):
cloudflared tunnel route dns -f venelx-api api.venelx.com
Or manually, in the Cloudflare dashboard → your zone → DNS:
| Type | Name | Target | Proxy |
|---|---|---|---|
| CNAME | api | <tunnel-id>.cfargotunnel.com | Proxied |
Route DNS only in the zone that owns the hostname.
Backend + Vercel env
# venelx-backend/.env
PUBLIC_DOMAIN=api.venelx.com
PUBLIC_API_URL=https://api.venelx.com
CORS_ORIGINS=https://app.venelx.com,https://*.vercel.app
# Vercel (venelx-frontend project)
NEXT_PUBLIC_API_URL=https://api.venelx.com
NEXT_PUBLIC_WS_URL=wss://api.venelx.com
Verify
curl -s https://api.venelx.com/api/health
dig @1.1.1.1 api.venelx.com +short
cloudflared tunnel info venelx-api
Expect {"status":"ok",...} and non-empty tunnel CONNECTIONS.
Troubleshooting
| Issue | Fix |
|---|---|
| 502 / connection refused | API not listening on :4000 — curl http://127.0.0.1:4000/api/health |
| Tunnel has no connections | cloudflared tunnel run venelx-api, or kickstart the LaunchDaemon |
| DNS points to an old tunnel | rerun ./scripts/setup-tunnel.sh (uses -f to overwrite the CNAME) |
| Wrong zone | route DNS only in the zone that owns api.venelx.com |
Local Mac can't resolve the name (dig empty, but dig @1.1.1.1 works) | router/ISP DNS hasn't picked up the record — see below |
Mac: dig empty but dig @1.1.1.1 works
Your router/ISP resolver has not picked up the new api CNAME yet (or cached an
NXDOMAIN). Flushing the local cache alone won't help until the Mac asks a resolver that
has the record. Point the Mac at Cloudflare's resolver:
sudo networksetup -setdnsservers Wi-Fi 1.1.1.1 1.0.0.1
sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder
dig api.venelx.com +short
curl -s https://api.venelx.com/api/health
Or via System Settings → Wi-Fi → Details → DNS — add 1.1.1.1 and 1.0.0.1.
(The repo also ships ./scripts/fix-local-dns.sh for this.)