What gets created
install.sh runs scripts/cloudflare.sh <hostname> to set up Cloudflare. The script is safe to re-run: it finds
existing objects by name or hostname and updates them. It works through these steps in order.
1. Tunnel#
A remotely managed Cloudflare Tunnel named
cloudflare-tmux. Change the name withTUNNEL_NAME.The tunnel's ingress rules, which tell Cloudflare where to send traffic. They're stored in Cloudflare, so there's no local
config.yml:[ { "hostname": "term.example.com", "service": "http://127.0.0.1:7681" }, { "service": "http_status:404" } ]The tunnel's run token, saved to
~/.cloudflared/cloudflare-tmux.tokenwith mode600. Thecloudflare-tmux-cloudflaredservice startscloudflaredwith it.
2. Cloudflare login method#
The script looks for the login method of type cloudflare, labelled "Sign in with Cloudflare". Zero Trust
usually creates it for you. If it's missing, the script creates it with restrict to account members on.
If it exists but isn't restricted, the script prints a warning. See Cloudflare login.
3. Access application and policy#
A self-hosted Access application for the hostname:
| Setting | Value | Why |
|---|---|---|
| Domain | term.example.com |
the whole hostname is protected |
| Login methods | Cloudflare only | no one-time PIN codes or other providers |
| Instant auth | on | goes straight to the Cloudflare sign-in |
| Session duration | ACCESS_SESSION, default 24h |
how often people sign in again |
| Cookie | HttpOnly, SameSite=Lax |
page scripts can't read it |
| Policy "Mapped Cloudflare accounts" | Allow each email in IDENTITIES |
only listed people get in |
The script then reads two values from Cloudflare and writes them to ~/.config/cloudflare-tmux/env: the
application's AUD tag as CF_ACCESS_AUD, and your team domain as CF_TEAM_DOMAIN. The app uses both
to verify logins.
Important
The Access application is created before the DNS record. The hostname therefore never resolves without protection, even if you stop the script halfway.
4. DNS record#
A proxied CNAME from term.example.com to <tunnel-id>.cfargotunnel.com, with the comment
cloudflare-tmux tunnel. An existing record with that name is replaced, so don't pick a hostname you
already use.
5. Local config#
~/.config/cloudflare-tmux/env, mode 600, gets PUBLIC_HOST, TUNNEL_NAME, PORT, CF_TEAM_DOMAIN and
CF_ACCESS_AUD. IDENTITIES is whatever you entered.
Doing it by hand#
If you'd rather not give the script an API token, make the same setup in the dashboard:
- Create the tunnel. Zero Trust → Networks → Tunnels → Create a tunnel → Cloudflared, named
cloudflare-tmux. - Save its token. Copy the long string after
--tokenin the install command it shows into~/.cloudflared/cloudflare-tmux.token. - Create the Access application. Zero Trust → Access → Applications → Add → Self-hosted: domain
term.example.com, login method Cloudflare only, instant auth on. Add an Allow policy with Include → Emails for each person. - Fill in the config. Copy the application's Application Audience (AUD) Tag into
~/.config/cloudflare-tmux/envasCF_ACCESS_AUD. SetCF_TEAM_DOMAIN=<team>.cloudflareaccess.comandIDENTITIES=.... - Add the public hostname. In the tunnel, add
term.example.comwith serviceHTTPand URL127.0.0.1:7681. This also creates the DNS record. - Install the services. Run
scripts/setup.sh, andscripts/install-system.shfor multi-user.
Warning
Create the Access application (step 3) before you add the public hostname (step 5). Otherwise the shell is briefly reachable without Access. The app would still reject requests without a valid login, but don't rely on a single layer.