How To Install Cloudflare Tunnel on Ubuntu 26.04 LTS

Install Cloudflare Tunnel on Ubuntu 26.04

Exposing a web application to the internet used to mean one thing: punching a hole in your firewall and praying nothing scans port 443 before you finish hardening the box. That approach still works, technically, but anyone who has spent a few years babysitting production servers knows how much attack surface it creates. Cloudflare Tunnel flips that model entirely — instead of opening inbound ports, your server reaches out to Cloudflare’s edge and establishes an encrypted, outbound-only connection. No open ports, no NAT rules, no dynamic DNS scripts running on a cron job at 2 AM.

This guide walks through installing and configuring Cloudflare Tunnel — specifically the cloudflared daemon — on Ubuntu 26.04 LTS, the latest long-term support release. Whether you’re running a WordPress stack behind Nginx, a private SSH bastion, or an internal dashboard that has no business being publicly routable, the underlying installation process is the same. What changes is how you configure ingress rules and how aggressively you lock things down afterward.

A quick reality check before diving in: Cloudflare Tunnel is not a silver bullet. It shifts trust from your perimeter firewall to Cloudflare’s network, which is a reasonable trade for most teams, but it’s still a dependency you need to monitor. If cloudflared crashes and no one notices, your app effectively goes dark even though the origin server is perfectly healthy. That’s a failure mode traditional reverse proxies don’t have, and it’s worth keeping in mind as you build out your systemd services and alerting.

By the end of this article, you’ll have a working tunnel running as a persistent systemd service, DNS routed correctly through Cloudflare, and a checklist of security and performance tweaks that actually matter in production — not just theoretical best practices copy-pasted from documentation.

What Is Cloudflare Tunnel and Why It Matters for Ubuntu Servers

Cloudflare Tunnel (the product) is powered by cloudflared (the binary), a lightweight daemon that creates a persistent, encrypted connection between your origin server and Cloudflare’s global edge network. Traffic flows through Cloudflare first, then gets proxied to your local service over the tunnel — your server never needs a public IP listening on any port.

For sysadmins managing WordPress installs, internal tools, or staging environments, this solves a genuinely annoying problem: exposing services without exposing your server’s actual network footprint. All tunnel connections use TLS 1.3 with post-quantum encryption by default, which is a meaningful upgrade over rolling your own stunnel or relying solely on Let’s Encrypt at the origin.

How It Differs From a Traditional Reverse Proxy Setup

A standard Nginx reverse proxy still requires an open inbound port (usually 443) and a public-facing IP that’s discoverable via DNS. Cloudflare Tunnel removes that requirement entirely — DNS points to a Cloudflare-managed CNAME, not your server’s IP, so port scanners hit a dead end. You can still run Nginx locally for TLS termination between cloudflared and your app if you want defense in depth, but it’s no longer strictly necessary for the internet-facing side.

Prerequisites Before You Start

Get these sorted before touching a terminal — skipping prep work is where most tunnel setups go sideways.

  • A Cloudflare account with a domain already added and its nameservers pointed to Cloudflare
  • An Ubuntu 26.04 LTS server (VM, bare metal, or cloud instance) with outbound internet access on port 443
  • Root or sudo privileges on the target machine
  • A local service already running that you want to expose (Nginx, Apache, a Node app, whatever)
  • Basic familiarity with systemd, since we’ll be running cloudflared as a managed service

One thing that trips people up: Cloudflare Tunnel needs outbound HTTPS access, not inbound. If your server sits behind a corporate proxy or a restrictive egress firewall, you’ll need to allowlist Cloudflare’s edge IP ranges or configure cloudflared to use your proxy explicitly.

Step-by-Step: Installing Cloudflared on Ubuntu 26.04 LTS

Step 1: Update the System

Always start clean. Skipping this step on a fresh Ubuntu 26.04 install is a common source of weird dependency conflicts later.

sudo apt update && sudo apt upgrade -y

Step 2: Add Cloudflare’s Package Signing Key

Cloudflare maintains its own APT repository, which is the cleanest way to install cloudflared and keep it updated through your normal patch cycle rather than manually downloading binaries every time.

sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null

Step 3: Add the Cloudflare APT Repository

echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main' | sudo tee /etc/apt/sources.list.d/cloudflared.list

Ubuntu 26.04’s codename doesn’t need to match anything in Cloudflare’s repo — the any distribution tag in the repo definition handles that cleanly, which saves you from repo-mismatch errors that plagued earlier Ubuntu releases when Cloudflare hadn’t yet tagged a new codename.

Step 4: Install Cloudflared

sudo apt update
sudo apt install cloudflared -y

Confirm the install:

cloudflared --version

If you see a version string with a recent build date, you’re good. If apt throws a 404 on the repo URL, double-check the keyring path — that’s the single most common install failure people report.

Step 5: Authenticate Cloudflared With Your Cloudflare Account

cloudflared tunnel login

This opens a browser-based auth flow (or gives you a URL to paste into a browser if you’re on a headless server). After you select the hostname/domain you want to associate, it drops a cert.pem file into ~/.cloudflared/. That certificate is what lets your server create tunnels under your account — treat it like an SSH private key, not a throwaway file.

Step 6: Create a Named Tunnel

cloudflared tunnel create production-web

This generates a UUID for the tunnel and a credentials JSON file, also stored in ~/.cloudflared/. Name your tunnels meaningfully — production-web, staging-api, internal-dashboard — because six months from now when you’re troubleshooting at 11 PM, “tunnel-3” tells you nothing.

Verify it exists:

cloudflared tunnel list

Step 7: Create the Configuration File

sudo mkdir -p /etc/cloudflared
sudo nano /etc/cloudflared/config.yml

A minimal but production-sane config looks like this:

tunnel: <TUNNEL-UUID>
credentials-file: /root/.cloudflared/<TUNNEL-UUID>.json

ingress:
  - hostname: app.yourdomain.com
    service: http://localhost:8080
  - hostname: ssh.yourdomain.com
    service: ssh://localhost:22
  - service: http_status:404

That last line is non-negotiable — Cloudflare requires a catch-all rule at the end of your ingress list, and it should return a 404 for anything that doesn’t match a defined hostname. Forget it, and cloudflared will refuse to start with a config validation error.

Step 8: Route DNS Through the Tunnel

cloudflared tunnel route dns production-web app.yourdomain.com

This creates a CNAME record pointing your subdomain to <UUID>.cfargotunnel.com automatically — no manual DNS editing required, and no risk of a typo in an A record.

Step 9: Install and Run Cloudflared as a Systemd Service

This is the step people skip and regret. Running the tunnel manually in a terminal session means it dies the moment you close the SSH connection.

sudo cloudflared service install
sudo systemctl enable --now cloudflared
sudo systemctl status cloudflared

The service install command reads your existing /etc/cloudflared/config.yml, registers cloudflared as a proper systemd unit, and ensures it restarts automatically on boot or crash. Any time you edit the config file afterward, restart the service to load changes:

sudo systemctl restart cloudflared

Real-World Use Cases

Tunnel isn’t just for hobbyists exposing a home lab. In production environments, it solves practical problems:

  • WordPress staging environments behind auth-gated tunnels, so client previews don’t need VPN access
  • Internal admin dashboards (Grafana, phpMyAdmin, Streamlit apps) exposed only to specific Cloudflare Access policies rather than the open internet
  • SSH bastion access for remote teams without maintaining a jump box with a public IP
  • API endpoints for CI/CD webhooks that need to be reachable but shouldn’t sit on a server with open inbound ports

For a technical SEO workflow specifically, this is genuinely useful for exposing a headless crawler dashboard or a Streamlit-based rank tracker to a client without provisioning a full VPN setup.

High Availability: Running Multiple Replicas

A single cloudflared process is a single point of failure. For anything customer-facing, Cloudflare recommends deploying multiple replicas — run cloudflared on two or more machines using the same tunnel credentials, and Cloudflare’s edge automatically load-balances and fails over between them. This is the same tunnel UUID, same credentials file, copied to a second server, running as its own systemd service. If one origin goes down during a traffic spike or a kernel update reboot, the other keeps serving without any DNS propagation delay.

Security Hardening for Production Tunnels

Run Cloudflared as a Non-Root User

By default, the service installer often runs as root. In production, create a dedicated system user with minimal privileges:

sudo useradd -r -s /usr/sbin/nologin cloudflared
sudo chown -R cloudflared:cloudflared /etc/cloudflared

Then adjust the systemd unit’s User= directive accordingly and reload:

sudo systemctl daemon-reload
sudo systemctl restart cloudflared

Least-privilege execution is one of Cloudflare’s own recommended hardening steps, and it’s cheap insurance against a compromised binary having full root access.

Lock Down Egress-Only Firewall Rules

Since Tunnel only needs outbound connectivity, configure your firewall (ufw, iptables, or your cloud provider’s security groups) to allow outbound HTTPS to Cloudflare’s IP ranges while denying all unsolicited inbound traffic:

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow out 443/tcp
sudo ufw enable

This is the actual security payoff of Tunnel — a fully closed inbound firewall on a server that’s still serving live traffic to the public internet.

Protect the Credentials File and cert.pem

The tunnel credentials JSON and cert.pem are effectively bearer tokens. Anyone with access to those files can spin up a tunnel under your account.

chmod 600 /root/.cloudflared/*.json /root/.cloudflared/cert.pem

Store backups encrypted, never commit them to a repo (yes, this happens more often than you’d think), and rotate them if a server is ever decommissioned.

Layer Cloudflare Access on Top

For anything internal — admin panels, staging environments, monitoring dashboards — pair Tunnel with Cloudflare Access policies requiring authentication (SSO, one-time PIN, or hardware key) before traffic ever reaches your origin. This turns your tunnel into a genuine Zero Trust boundary instead of just a NAT replacement.

Performance Tuning Considerations

Tunnel overhead is usually negligible for typical web traffic, but at scale, a few tweaks matter:

  • CPU: cloudflared is single-process but can consume noticeable CPU under high concurrent connection counts; monitor with htop and consider replicas before vertically scaling a single origin
  • Network latency: since traffic now hops through Cloudflare’s edge, users closest to a Cloudflare PoP see minimal added latency, but geographically distant clients may notice a slight increase compared to a direct connection — usually single-digit milliseconds, rarely worth worrying about
  • Connection limits: for high-traffic origins, increase ha-connections in your config to open more simultaneous connections to Cloudflare’s edge, spreading load more evenly
  • Disk I/O: logging verbosity matters — running cloudflared with --loglevel debug in production generates enormous log volume and unnecessary disk writes; stick to info or warn unless actively debugging

Troubleshooting Common Errors

Issue Likely Cause Fix
apt update returns 404 for cloudflared repo Keyring path or repo line mismatch Re-verify /etc/apt/sources.list.d/cloudflared.list and keyring file permissions
Tunnel shows “Down” in dashboard despite service running Config file not reloaded Run sudo systemctl restart cloudflared after any config.yml edit
Error: no ingress rules were defined Missing catch-all http_status:404 rule Add the catch-all as the last ingress entry
SSH via tunnel hangs or times out Wrong service protocol in ingress (using http:// instead of ssh://) Correct the service scheme to match the actual protocol
Systemd service fails immediately after service install Config file path mismatch or missing credentials file Confirm credentials-file path in config.yml matches actual file location and permissions
DNS not resolving to tunnel CNAME propagation delay or wrong hostname routed Run cloudflared tunnel route dns again and check dig output after a few minutes
r00t is a Linux Systems Administrator and open-source advocate with over ten years of hands-on experience in server infrastructure, system hardening, and performance tuning. Having worked across distributions such as Debian, Arch, RHEL, and Ubuntu, he brings real-world depth to every article published on this blog. r00t writes to bridge the gap between complex sysadmin concepts and practical, everyday application — whether you are configuring your first server or optimizing a production environment. Based in New York, US, he is a firm believer that knowledge, like open-source software, is best when shared freely.

Related Posts