<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Andrew</title>
    <description>The latest articles on Forem by Andrew (@casablanque).</description>
    <link>https://forem.com/casablanque</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3952987%2F2ff2213e-26a5-4280-ab44-d1a027ff62b8.png</url>
      <title>Forem: Andrew</title>
      <link>https://forem.com/casablanque</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/casablanque"/>
    <language>en</language>
    <item>
      <title>CLI wrapper for Cloudflare Tunnel with Zero Trust</title>
      <dc:creator>Andrew</dc:creator>
      <pubDate>Tue, 26 May 2026 17:34:57 +0000</pubDate>
      <link>https://forem.com/casablanque/cli-wrapper-for-cloudflare-tunnel-with-zero-trust-16h4</link>
      <guid>https://forem.com/casablanque/cli-wrapper-for-cloudflare-tunnel-with-zero-trust-16h4</guid>
      <description>&lt;p&gt;I got tired of configuring Cloudflare Zero Trust manually, so I built a 15s CLI wrapper. &lt;br&gt;
Every time I wanted to expose a new local service (like Grafana or a dev API) securely, the routine was always the same:&lt;br&gt;
Open Cloudflare Dashboard.&lt;br&gt;
Create a new Tunnel.&lt;br&gt;
Configure Ingress rules.&lt;br&gt;
Add a DNS CNAME record.&lt;br&gt;
Switch to the Zero Trust panel.&lt;br&gt;
Create an Access Application.&lt;br&gt;
Set up an Access Policy to restrict access to my email.&lt;/p&gt;

&lt;p&gt;It’s an amazing, enterprise-grade security stack, but doing this manually for the 10th time just to test something is an absolute UX nightmare.&lt;br&gt;
I wanted something as simple as ngrok, but with Cloudflare's Zero Trust protection under the hood. Since I couldn't find a lightweight tool that does exactly this, I wrote zt in Go.&lt;/p&gt;

&lt;p&gt;How it works&lt;br&gt;
Now, when I need to share or expose a local service, I just run:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;zt up grafana 3000&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In about 15 seconds, it automatically handles the whole chain:&lt;/p&gt;

&lt;p&gt;Creates the Cloudflare Tunnel.&lt;br&gt;
Sets up DNS and Ingress.&lt;br&gt;
Locks it behind Cloudflare Access (asks for email OTP by default).&lt;br&gt;
Fires up cloudflared in the background and saves the state locally.&lt;/p&gt;

&lt;p&gt;If I need it to be completely public (like an webhook endpoint), I just pass a flag: &lt;code&gt;zt up api 8080 --public&lt;/code&gt;. If I want to share it with specific colleagues: &lt;code&gt;--allow mail@example.com&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;When I'm done, zt down grafana wipes everything clean and stops the process.&lt;/p&gt;

&lt;p&gt;The Stack&lt;br&gt;
It’s a single binary written in Go, using the official Cloudflare API package. Configuration and state are kept locally in ~/.zt-config.json and ~/.zt-state.json (secured with 0600 permissions).&lt;/p&gt;

&lt;p&gt;I’ve been using it daily in my home lab and dev environment, and it has saved me hours of clicking through web UIs.&lt;/p&gt;

&lt;p&gt;The project is completely open-source (MIT). If you're managing self-hosted apps or often need to expose local ports securely, feel free to check it out, open an issue, or drop a star!&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/casablanque-code" rel="noopener noreferrer"&gt;
        casablanque-code
      &lt;/a&gt; / &lt;a href="https://github.com/casablanque-code/cfzt" rel="noopener noreferrer"&gt;
        cfzt
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      cloudflare tunnel zero trust UX
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;zt — Zero Trust tunnel manager&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;One command to expose a local service through Cloudflare Zero Trust.&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;zt up grafana 3000
# → https://grafana.yourdomain.com  (ZT-protected, running in 15s)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it does&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;zt up &amp;lt;name&amp;gt; &amp;lt;port&amp;gt;&lt;/code&gt; automatically:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Creates a Cloudflare Tunnel&lt;/li&gt;
&lt;li&gt;Configures ingress rules&lt;/li&gt;
&lt;li&gt;Creates a CNAME DNS record&lt;/li&gt;
&lt;li&gt;Creates a Zero Trust Access application&lt;/li&gt;
&lt;li&gt;Starts &lt;code&gt;cloudflared&lt;/code&gt; in the background&lt;/li&gt;
&lt;li&gt;Saves state locally&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;zt down &amp;lt;name&amp;gt;&lt;/code&gt; reverses all of the above.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Prerequisites&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;A domain on Cloudflare&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;cloudflared&lt;/code&gt;&lt;/a&gt; installed and in PATH&lt;/li&gt;
&lt;li&gt;A Cloudflare API token with the following permissions:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Account / Cloudflare Tunnel / Edit&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Zone / DNS / Edit&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Account / Access: Apps and Policies / Edit&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Creating the API token&lt;/h3&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Cloudflare dashboard → &lt;strong&gt;My Profile&lt;/strong&gt; → &lt;strong&gt;API Tokens&lt;/strong&gt; → &lt;strong&gt;Create Token&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;Custom token&lt;/strong&gt;, add the permissions above&lt;/li&gt;
&lt;li&gt;Set Account Resources → your account&lt;/li&gt;
&lt;li&gt;Set Zone Resources → your domain&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Install&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Option A — go install&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;go install&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/casablanque-code/cfzt" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>go</category>
      <category>devops</category>
      <category>cloudflarechallenge</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
