How To Install Cloudflare Tunnel on Ubuntu 26.04 LTS

Install Cloudflare Tunnel on Ubuntu 26.04

Cloudflare Tunnel on Ubuntu 26.04 LTS is a small daemon called cloudflared. It opens outbound connections to Cloudflare’s network and lets you publish web apps, SSH and internal services without opening any inbound port. Install it from Cloudflare’s APT repository, authenticate with a tunnel token or a local credentials file, and run it under systemd.

Why Bother With a Tunnel at All?

Anyone who has run a public server for a few months knows the log file. Open /var/log/auth.log on a fresh VPS with port 22 exposed and you will find thousands of failed logins from addresses you have never heard of. Put a web server on port 443 and the scanners show up within minutes, probing for wp-login.php, .env, and whatever vulnerability was published last week.

The traditional answer is a firewall, fail2ban, a reverse proxy and a lot of patience. That works. It also leaves you with a public IP address that attackers can hit directly, even if the site sits behind a CDN, because origin IPs leak through old DNS records, mail headers and certificate transparency logs.

A tunnel flips the model. Instead of the internet connecting to your server, your server connects out to Cloudflare. Nothing listens publicly. The origin IP becomes irrelevant, and you can close ports 80 and 443 at the firewall completely. This is also why the setup suits home labs behind CGNAT, small offices with no static IP, and staging servers that should never be indexed or probed.

Ubuntu 26.04 LTS, codenamed Resolute Raccoon, was released on April 23, 2026, and Ubuntu lists standard support until 2031. That makes it a sensible base for a long-lived tunnel host. This guide walks through the full install, covers both management modes, and ends with the failures that actually happen in production.

What You Need Before Starting

Gather these first. It saves a lot of back and forth later.

  • A server running Ubuntu 26.04 LTS with sudo access. A 1 vCPU, 1 GB RAM VPS is enough for the tunnel itself.
  • A free Cloudflare account and a domain whose DNS is managed by Cloudflare.
  • An application to expose. A local web service on 127.0.0.1:8080 is a good test target.
  • Outbound connectivity from the server. No inbound ports are required.

Confirm the release before touching anything:

lsb_release -a
uname -r

Ubuntu’s version history lists an initial kernel of 7.0 for this release, so expect a 7.x kernel string. Then bring the system current:

sudo apt update && sudo apt full-upgrade -y
sudo apt install -y curl ca-certificates gnupg

Reboot if a new kernel was installed. It is boring advice, but a tunnel host that has been running on a stale kernel for months is how nasty surprises start.

Choose Your Management Mode First

Cloudflare Tunnel comes in two flavors, and picking the wrong one for your team creates friction later.

Aspect Remotely managed Locally managed
Where config lives Cloudflare dashboard config.yml on the server
Auth method Tunnel token Certificate plus credentials JSON
Install command cloudflared service install <TOKEN> cloudflared service install
Best for Quick setups, small teams Git-versioned infrastructure, automation
Ingress changes Edit in dashboard, applied live Edit file, restart service

Cloudflare’s current getting-started flow installs cloudflared and runs sudo cloudflared service install <TUNNEL_TOKEN> for the token-based approach. Locally managed tunnels require a CLI setup first, then a service install that reads your config file.

For a single server with a handful of hostnames, remotely managed is faster. For anything you want in Git, reviewed in pull requests and rebuilt by Ansible, go local. This article covers both, starting with the shared install.

Installing cloudflared From the APT Repository

Skip the random .deb download from GitHub. A repository install means apt upgrade keeps the daemon current, and that matters. Cloudflare only supports recent cloudflared versions, and an unattended tunnel that silently ages will eventually stop connecting.

Step 1: Add the signing key

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

This is the key path shown in Cloudflare’s own documentation. Keys get rotated occasionally, so if apt update complains about a signature later, check the Cloudflare packages page for the current key filename before assuming something is broken.

Step 2: Add the 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

The any in that line is deliberate. Cloudflare’s repository uses a generic distribution name, so the same line works across supported Debian and Ubuntu releases. That is handy on a brand new release like 26.04, where a codename-specific suite might not exist yet.

Step 3: Install and verify

sudo apt-get update
sudo apt-get install -y cloudflared
cloudflared --version

You should see a version string with a recent date. Check where the binary landed:

which cloudflared
apt-cache policy cloudflared

The signed-by option scopes the key to this one repository only. That is far safer than the old apt-key add habit, which trusted a key for every repository on the system. It is also the modern, supported way to add third-party sources.

Method A: Remotely Managed Tunnel (Token-Based)

This is the quickest path from nothing to a working public hostname.

Create the tunnel in the dashboard

In the Cloudflare dashboard, go to Networking, then Tunnels, and create a new tunnel. Give it a name that tells you which machine it belongs to, such as prod-web-01 or homelab-nuc. Six months from now, “my-tunnel-2” will mean nothing to you.

Choose Cloudflared as the connector type. The dashboard then displays an install command that includes a long token. Treat that token like a password. Anyone holding it can run your tunnel from any machine.

Install it as a service

sudo cloudflared service install eyJhIjoi...your-token...

That single command writes a systemd unit, enables it at boot and starts it. Check it:

sudo systemctl status cloudflared
journalctl -u cloudflared -n 50 --no-pager

You are looking for lines indicating registered tunnel connections. Cloudflare normally opens several connections to different edge locations for redundancy. Seeing four is normal.

Publish a route

Back in the dashboard, open your tunnel, find Routes and add a published application route. Fill in a subdomain, your domain, and the service URL, for example http://localhost:8080. Cloudflare creates the DNS record for you, a proxied CNAME pointing at the tunnel.

Test from anywhere:

curl -I https://app.example.com

If the application answers, you are done. No port forwarding, no certificates to issue on the origin, no public IP exposure.

Method B: Locally Managed Tunnel (Config File)

Teams that manage servers as code usually prefer this. The configuration is a plain text file you can diff, review and deploy.

Step 1: Authenticate

cloudflared tunnel login

This prints a URL. Open it in a browser, pick your domain, and authorize. A certificate file lands in ~/.cloudflared/cert.pem. On a headless server, copy the URL to your workstation. It works fine from any browser.

Step 2: Create the tunnel

cloudflared tunnel create prod-web-01

The output includes a UUID and the path to a credentials JSON file such as ~/.cloudflared/<UUID>.json. Back that file up in a secrets manager. If it is lost, you will need to create a new tunnel.

Step 3: Move files to a system location

Running a production service out of a personal home directory is asking for trouble. Put everything under /etc/cloudflared:

sudo mkdir -p /etc/cloudflared
sudo cp ~/.cloudflared/<UUID>.json /etc/cloudflared/
sudo chown root:root /etc/cloudflared/<UUID>.json
sudo chmod 600 /etc/cloudflared/<UUID>.json

Step 4: Write the config

# /etc/cloudflared/config.yml
tunnel: <UUID>
credentials-file: /etc/cloudflared/<UUID>.json

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

Two details matter here. The final catch-all rule is mandatory, because cloudflared refuses to start without one. And order counts: rules are evaluated top to bottom, so put specific hostnames before wildcards.

Validate before you run anything:

cloudflared tunnel ingress validate --config /etc/cloudflared/config.yml
cloudflared tunnel ingress rule https://app.example.com --config /etc/cloudflared/config.yml

The second command shows which rule would match a given URL. It catches ordering mistakes in seconds.

Step 5: Create DNS records

cloudflared tunnel route dns prod-web-01 app.example.com
cloudflared tunnel route dns prod-web-01 ssh.example.com

Step 6: Dry run in the foreground

sudo cloudflared tunnel --config /etc/cloudflared/config.yml run

Watch for errors, then hit Ctrl+C. Running once by hand saves you from debugging a systemd failure when the real problem is a typo in YAML.

Step 7: Install as a service

sudo cloudflared --config /etc/cloudflared/config.yml service install
sudo systemctl enable --now cloudflared

There is a trap here that bites many people. Cloudflare’s documentation notes that when the install runs under sudo, $HOME points to /root, so the service may fail to find a config sitting in /home/<USER>/.cloudflared/config.yml, and the fix is to pass the config path explicitly. Keeping the config in /etc/cloudflared avoids the whole problem.

Understanding the systemd Unit

Knowing what the unit looks like makes debugging much easier. A token-based unit looks roughly like this:

[Unit]
Description=Cloudflare Tunnel
After=network.target

[Service]
TimeoutStartSec=0
Type=notify
ExecStart=/usr/local/bin/cloudflared tunnel --loglevel info --logfile /var/log/cloudflared/cloudflared.log run --token <TOKEN VALUE>
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target

To change flags, Cloudflare recommends editing the unit with sudo systemctl edit --full cloudflared.service, then restarting. Note that the token appears in the command line, which means it is visible in ps output to local users. On a shared box, that is worth fixing. Use the TUNNEL_TOKEN environment variable instead, loaded from a root-only file:

sudo systemctl edit cloudflared
[Service]
EnvironmentFile=/etc/cloudflared/token.env

Keep the file at mode 600, owned by root. It is a small change, and it removes a secret from process listings and shell history.

Troubleshooting Common Errors

Most failures fall into a short list. Here is what they look like and how they are usually solved.

“failed to unmarshal tunnel credentials” or missing credentials file

The path in credentials-file is wrong, or permissions block the service. Check:

ls -l /etc/cloudflared/
sudo journalctl -u cloudflared -n 100 --no-pager

Use absolute paths. Relative paths and ~ behave differently under systemd than in your shell.

Service fails because it cannot find config.yml

This is the $HOME=/root problem described earlier. Move the config to /etc/cloudflared/config.yml or pass --config explicitly when installing the service.

“no ingress rules were defined” or the catch-all error

Your ingress block must end with a rule that has no hostname, such as - service: http_status:404. Validate with cloudflared tunnel ingress validate.

Error 1033 or 502 in the browser

Error 1033 generally means Cloudflare cannot reach a running tunnel for that hostname. Confirm the service is active and that DNS points to the right tunnel. A 502 usually means the tunnel is up but the local service is not answering. Test the origin directly:

curl -v http://localhost:8080

If that fails, the tunnel is innocent. Fix the application first.

Connection timeouts and “failed to dial to edge”

Outbound traffic is blocked. Check egress rules on the host and any upstream firewall, then test connectivity:

curl -sI https://www.cloudflare.com | head -n 1

If QUIC is blocked, switch to --protocol http2. Cloudflare’s documentation lists the required outbound ports and destinations, so compare them against your firewall rules.

Clock drift

Unusual, but real on VMs restored from snapshots. Skewed time breaks TLS handshakes. Check with timedatectl and make sure NTP sync is active.

The tunnel works, but websites feel slow

Look at the origin first. Run curl -w "%{time_starttransfer}\n" -o /dev/null -s http://localhost:8080 to measure time to first byte locally. If the origin itself takes two seconds, no tunnel setting will rescue it. If local timing is fast, review cache rules and edge location instead.

Config changes do nothing

After editing a local config, restart the service, as Cloudflare’s docs note:

sudo systemctl restart cloudflared

Remotely managed tunnels apply dashboard changes automatically, so a restart is rarely needed there.

r00t is an experienced Linux enthusiast and technical writer with a passion for open-source software. With years of hands-on experience in various Linux distributions, r00t has developed a deep understanding of the Linux ecosystem and its powerful tools. He holds certifications in SCE and has contributed to several open-source projects. r00t is dedicated to sharing her knowledge and expertise through well-researched and informative articles, helping others navigate the world of Linux with confidence.

Related Posts