Install Cloudflared and Create the Tunnel
This final stage installs Cloudflare’s cloudflared connector on the Raspberry Pi 5 and maps one public hostname to the local Nginx origin at http://127.0.0.1:8080. The connector makes outbound connections, so this design needs no inbound WAN rule and works with Starlink’s default IPv4 CGNAT. A public hostname is public unless an Access policy protects it; do not use this route for pfSense, SSH, storage, cameras, or private administration.
Prerequisites and version checks
- Complete Configure Nginx on Raspberry Pi 5. The origin must answer locally with the exact Host header intended for publication.
- Control a domain in a Cloudflare account and know which account and zone should own the tunnel. Use a dedicated administrator or service owner rather than sharing a personal password.
- Keep the Netgate DMZ egress policy narrow. Cloudflare’s current requirements, endpoints, and protocol behavior can change; consult the linked documentation rather than hard-coding an old IP list.
- Have a trusted private recovery path. Do not make the public tunnel the only way to administer or repair the Pi.
cat /etc/os-release
uname -m
ip -4 route
nginx -v 2>&1
curl --fail --silent --show-error -H 'Host: www.example.net' http://127.0.0.1:8080/
cloudflared --version 2>/dev/null || true
Cloudflare publishes packages for supported distributions and architectures. Verify that the current package supports this Pi’s architecture and Bookworm release before installation. Do not download an arbitrary binary from a search result or use a command copied from an obsolete guide.
Install from Cloudflare’s official source
For a verified Debian Bookworm-compatible host, the current Cloudflare package repository uses a signed key and a Bookworm channel. Compare these commands with Cloudflare’s current package page before running them; use the channel shown there if it has changed.
sudo install -d -m 0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared bookworm main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt update
sudo apt install cloudflared
cloudflared --version
apt-cache policy cloudflared
command -v cloudflared
If the package is unavailable for the verified architecture, stop and use the current Cloudflare installation page for its supported package or release artifact. Do not bypass signature verification or grant the binary unexplained privileges. Keep the package source documented so it can be updated and removed cleanly. The next.pkg.cloudflare.com repository is for testing or pre-release versions; do not use it for this production connector unless you have deliberately accepted that risk.
Create a named tunnel
Cloudflare supports dashboard-managed and locally managed tunnel workflows. The dashboard token workflow is shown here because it avoids putting a long-lived account certificate on the Pi. In the Cloudflare Zero Trust dashboard, create a named tunnel, choose Cloudflared, and copy the install command or token only to the Pi’s protected administrative session. The token remains valid until revoked; the exact dashboard labels may change.
- Choose a clear tunnel name such as
pi-dmz-web; do not include a secret or private address in the name. - Select the
cloudflaredconnector and record the tunnel UUID in the change record. - Keep the token out of command history where practical. If the dashboard provides a token-bearing install command, paste it only into the terminal session that will immediately install the service, and clear history according to your shell’s documented procedure.
- Verify the connector appears healthy in Cloudflare before creating a public hostname.
For a locally managed tunnel, follow Cloudflare’s current login, certificate, credentials-file, and service instructions exactly. A credentials JSON file is also a secret. Store it with root ownership and mode 0600, outside the web root, and back it up only in an encrypted secret store. Where the current supported workflow permits, prefer that root-owned 0600 credentials file or a root-owned 0600 systemd environment file over embedding a token on a command line. Do not invent an environment variable or configuration format that Cloudflare does not document.
Install and protect the connector service
Use the official command presented by the current Cloudflare dashboard or documentation. A typical package installation uses a system service; confirm the service name and generated unit before enabling it:
systemctl list-unit-files | grep -i cloudflared || true
sudo cloudflared service install '<TUNNEL_TOKEN_FROM_CLOUDFLARE_DASHBOARD>'
sudo systemctl status cloudflared --no-pager
sudo systemctl enable --now cloudflared
sudo systemctl is-enabled cloudflared
sudo systemctl is-active cloudflared
Run the token-bearing command only if the current Cloudflare dashboard procedure explicitly requires cloudflared service install <TOKEN>; otherwise use Cloudflare’s documented credentials-file or environment-file workflow and do not substitute this command. Replace the bracketed value only in a protected terminal, do not commit the command, and remove it from shell history. The dashboard token is long-lived until revoked and may be embedded in the generated systemd unit’s ExecStart. It may therefore be visible to any local user permitted to run systemctl cat or systemctl show; unit-file permissions do not conceal it from those users. Restrict local service administration and access to those inspection commands to root or explicitly trusted privileged administrators through the host’s sudo/polkit policy, and verify that policy. Prefer Cloudflare’s supported credentials-file or environment-file mechanism when available, with a root-owned 0600 secret file. If a token is exposed to an unauthorized user, revoke or rotate it immediately and recreate the connector. If the command creates the service under a different unit name, use that documented name instead of inventing a second service. Inspect the unit and verify actual permissions rather than assuming them:
sudo systemctl cat cloudflared
sudo systemctl show cloudflared -p User -p ExecStart
sudo stat -c '%A %U:%G %n' /etc/systemd/system/cloudflared.service 2>/dev/null || true
sudo find /etc/cloudflared -maxdepth 1 -type f -exec stat -c '%A %U:%G %n' {} \; 2>/dev/null || true
sudo ss -lntup
sudo journalctl -u cloudflared -n 80 --no-pager
Do not expose a local metrics or diagnostic listener beyond loopback unless the monitoring policy explicitly requires it. Do not add inbound firewall rules for the connector; its useful connections are outbound.
Map one hostname to the loopback origin
In the tunnel’s Public Hostnames or current equivalent route editor, add exactly one hostname, for example www.example.net. Select the intended DNS zone and configure the service as HTTP with URL 127.0.0.1:8080. Preserve the hostname Nginx expects, or explicitly configure the origin request Host header using the current Cloudflare option. Do not point the route at 192.168.60.10:8080 when Nginx is intentionally loopback-only.
Use a final catch-all rule that returns an error rather than forwarding unknown hostnames if the selected workflow supports ingress rules. Keep one route while validating; wildcard hosts and multiple origins increase the blast radius of a configuration mistake.
Cloudflare may create or update the DNS record as part of the hostname workflow. Review the resulting record, proxy status, TLS mode, and hostname spelling. Do not create an unrelated A or AAAA record pointing directly to the Starlink or DMZ address. The Pi’s RFC 1918 address must never be published in public DNS.
Protect public and private audiences separately
For a deliberately public site, harden the application and publish only the intended hostname. For a household dashboard or administrative application, create a Cloudflare Access self-hosted application and policy before inviting users, require MFA where available, and test an authorized and unauthorized account. Keep Tailscale for private administration and an independent local recovery path; do not place pfSense WebConfigurator or SSH behind a casual public route.
Cloudflare Access controls the edge, while the origin still needs its own authorization and input validation. Never trust an identity header from an arbitrary client. If the application validates Cloudflare identity assertions, configure the documented supported mechanism and reject missing or invalid assertions.
Keep the IPv6 boundary explicit
A tunnel’s outbound behavior does not make every address on the Pi safe to publish. In this IPv4-only design, keep IPv6 disabled on the DMZ host and do not create a public AAAA record. If IPv6 is later enabled, verify the delegated prefix, host firewall, Netgate IPv6 rules, and tunnel egress separately; IPv4 NAT and IPv4 blocks do not constrain routed IPv6. Test the published hostname over both protocol families from an external network before declaring the service ready.
Validate the complete path
- On the Pi, run
curl -H 'Host: www.example.net' http://127.0.0.1:8080/and verify the expected origin content. - Confirm
systemctl is-active cloudflared, the Cloudflare dashboard connector health, and recent service logs show successful connections without repeated authentication failures. - From a genuinely external network, request
https://www.example.net/and verify the certificate, hostname, content, redirects, cookies, and error behavior. - Confirm the old public IP, direct
192.168.60.10:8080, an unknown hostname, and pfSense management are not alternate paths to the origin. - Review Nginx and Cloudflare logs for expected client information, origin errors, unexpected paths, and sensitive data. Check that the tunnel service survives a reboot.
- Test the documented emergency procedure with the hostname route disabled; private administration and local console recovery must remain available.
Rollback and credential rotation
To unpublish safely, first remove or disable the public hostname route and its DNS record in Cloudflare. Confirm the hostname no longer reaches the origin, then stop and disable the connector service. Remove the connector from the Cloudflare account, delete unused credentials, and leave Nginx bound only to loopback or stop it if the origin is no longer needed. Preserve logs and the change record according to your retention policy.
If a token, credentials file, account session, or private key was exposed, treat it as compromised: revoke or rotate it in Cloudflare, remove the old connector, install a fresh credential through the current official workflow, and verify that the old connector no longer reconnects. Do not rely on deleting shell history alone.
dispelled