avatar vian's notes

Learning in public, one post at a time

  • HOME
  • CATEGORIES
  • TAGS
  • ARCHIVES
  • ABOUT
Home The Tunnel
Post

The Tunnel

Posted Jun 24, 2026 Updated Jul 12, 2026
By Alvian
2 min read
The Tunnel
The Tunnel

I typed http://localhost:20128 into Warp’s custom provider field. Clicked Save. The field turned red.

Silent rejection. Not “connection refused” — the UI wouldn’t even accept a localhost address. curl worked fine, but Warp’s Oz agents don’t run on my machine. They live in an isolated context that can’t see localhost.

My routing layer from the 9router fork was ready. Invisible.

A Wrong Turn

I’d tried local-tunnel before — simple, but URLs rotate on every restart. Unusable for anything permanent.

Cloudflare Quick Tunnel was too easy to skip. No account, no config:

1
cloudflared tunnel --url http://localhost:20128

Got a URL. Pasted it. Connected. Triumph.

Then I rebooted. Different URL. Pasted again. Rebooted again. Different again.

Quick Tunnel URLs rotate on every restart. I could update manually, but that’s a loop I refused to live in.

Two Layers, One Goal

Caddy terminates TLS with self-signed certs for picky clients. Three lines of config.

Cloudflare Tunnel creates an outbound connection from my machine to Cloudflare’s edge. No inbound ports. No NAT traversal.

1
2
3
4
5
6
7
# ~/.cloudflared/config.yml
tunnel: abc123-def456
credentials-file: /Users/me/.cloudflared/abc123-def456.json
ingress:
  - hostname: api.mydomain.com
    service: http://localhost:20128
  - service: http_status:404

Two CLI commands. One config file. Done.

Surviving macOS

The tunnel needs to outlast sleep, wake, reboot, coffee-shop WiFi. I used launchd:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
 "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.9router.tunnel</string>
    <key>ProgramArguments</key>
    <array>
        <string>/opt/homebrew/bin/cloudflared</string>
        <string>tunnel</string>
        <string>run</string>
    </array>
    <key>KeepAlive</key>
    <true/>
    <key>RunAtLoad</key>
    <true/>
    <key>StandardOutPath</key>
    <string>/tmp/cloudflared.log</string>
    <key>StandardErrorPath</key>
    <string>/tmp/cloudflared.err</string>
</dict>
</plist>

KeepAlive respawns on crash. RunAtLoad starts at login. Load once, permanent.

What Breaks

Sleep/wake kills the network stack. cloudflared disconnects. KeepAlive respawns it, but there’s a ~15-second gap. For automated jobs, a health check handles it.

Log debugging: tail -f /tmp/cloudflared.log. 90% is network flakiness. 10% is auth tokens — cloudflared tunnel login.

What Changed

Before the tunnel: running prompts twice. Once in the IDE, once locally. Burning API credits on iteration.

After: Warp agents hit https://api.mydomain.com/v1. Every request lands on my local server. The tunnel disappears.

API costs dropped from ~$3/day to ~$0 (domain is $10/year). Iteration time went from 3-5 seconds to under 1 second. Stopped thinking about rate limits.

Infrastructure should be boring. The tunnel is.

I wrote about how Warp uses this endpoint in Warp Came Back Around.

cloudflare caddy tunnel infrastructure
This post is licensed under CC BY-NC 4.0 by the author.
Share

Recently Updated

  • Building Memory Into 9router: A Proxy-Layer Experiment
  • The Tunnel
  • This Is My ADE
  • Why I Left GitHub Copilot
  • The Router My Colleague Showed Me

Trending Tags

9router tooling technical personal ai warp ade analytics postmortem agents

Contents

Older
What I Changed in 9router
Newer
Warp Came Back Around

© 2026 Alvian. Some rights reserved.

This blog is open source — view on GitHub

Using the Chirpy theme for Jekyll.

Trending Tags

9router tooling technical personal ai warp ade analytics postmortem agents