A local web application does not need a public IP address or an open router port to use a real hostname. Installing Cloudflare Tunnel on Ubuntu gives the host an outbound-only connection to Cloudflare, which can publish an HTTP service while the origin remains behind NAT or a restrictive inbound firewall.
Use Cloudflare’s stable APT repository for the cloudflared package. A temporary Quick Tunnel proves that the connector can reach a local service, while a remotely managed tunnel installs cloudflared.service for a persistent hostname. The production path requires a Cloudflare account, a domain using Cloudflare DNS, and a tunnel token generated in the Cloudflare dashboard.
Install Cloudflare Tunnel on Ubuntu
Install cloudflared from Cloudflare’s APT Repository
Use Cloudflare’s stable repository as the single package source so APT owns installation and later updates. Its generic any suite avoids Ubuntu codename substitutions; keep the key, source file, and candidate verification in sequence so APT can reject unsigned or mismatched repository metadata.
Prepare Ubuntu for the Cloudflare Repository
These package commands require an administrative account. Configure one first if sudo is unavailable; the separate instructions explain how to add a user to sudoers on Ubuntu.
This server-oriented workflow intentionally keeps the system-wide upgrade decision separate from the package installation. Apply pending Ubuntu upgrades during an approved maintenance window before continuing when your update policy requires them.
Refresh the APT package index, then install the tools needed to retrieve and inspect Cloudflare’s signing key:
sudo apt update
sudo apt install ca-certificates curl gpg
Install the Cloudflare Repository Key
The official Cloudflare package repository publishes the current key as cloudflare-main.gpg. Download it as your normal user, make sure GnuPG can parse it as OpenPGP data, then place the key under the vendor-owned path used by the repository:
(
set -e
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
install -m 0700 -d "$tmpdir/gnupg"
curl -fsSLo "$tmpdir/cloudflare-main.gpg" \
https://pkg.cloudflare.com/cloudflare-main.gpg
GNUPGHOME="$tmpdir/gnupg" gpg --batch --show-keys \
--with-fingerprint "$tmpdir/cloudflare-main.gpg"
sudo install -m 0755 -d /usr/share/keyrings
sudo install -m 0644 "$tmpdir/cloudflare-main.gpg" \
/usr/share/keyrings/cloudflare-main.gpg
)
The key listing should identify Cloudflare software packaging and print at least one full fingerprint. This structural check confirms the response is readable OpenPGP data; trust still comes from retrieving it over HTTPS from Cloudflare’s package host. Stop if curl fails or GnuPG reports invalid key data.
Add the Cloudflare APT Source
Cloudflare’s recommended Debian-family source uses the generic any suite instead of an Ubuntu codename. The source below points to the same stable repository and signing key as Cloudflare’s package instructions, but stores the entry in APT’s DEB822 format for clearer auditing and cleanup.
Check for an existing Cloudflare package source before creating a new one:
grep -RIn --include='*.list' --include='*.sources' \
"pkg.cloudflare.com/cloudflared" \
/etc/apt/sources.list /etc/apt/sources.list.d 2>/dev/null || true
No output means the source is not already configured. If a path appears, inspect that file instead of adding a second entry. Keep an existing definition when its URI and Signed-By path match this method; resolve mismatched definitions before continuing.
This installation path covers 64-bit x86 (
amd64) and 64-bit Arm (arm64) systems. The guard stops before changing APT whendpkg --print-architecturereturns another value.
arch="$(dpkg --print-architecture)"
case "$arch" in
amd64|arm64)
printf '%s\n' \
'Types: deb' \
'URIs: https://pkg.cloudflare.com/cloudflared' \
'Suites: any' \
'Components: main' \
"Architectures: ${arch}" \
'Signed-By: /usr/share/keyrings/cloudflare-main.gpg' \
| sudo tee /etc/apt/sources.list.d/cloudflared.sources > /dev/null && \
sudo chmod 0644 /etc/apt/sources.list.d/cloudflared.sources
;;
*)
printf 'Unsupported architecture for this installation path: %s\n' "$arch" >&2
false
;;
esac
Refresh APT and inspect the package candidate before installation:
sudo apt update
apt-cache policy cloudflared | sed -n '1,12p'
A usable result lists a real candidate instead of (none) and identifies pkg.cloudflare.com/cloudflared as its source. Do not continue if APT reports a signing error, a conflicting Signed-By value, or no package candidate.
Install and Verify cloudflared
Install the package after confirming the candidate comes from Cloudflare’s repository:
sudo apt install cloudflared
Check the command path, installed version, package architecture, and package-manager ownership:
cloudflared --version
cloudflared_path="$(command -v cloudflared)"
printf 'Command path: %s\nResolved path: %s\n' \
"$cloudflared_path" "$(readlink -f "$cloudflared_path")"
dpkg-query -S "$(readlink -f "$cloudflared_path")"
dpkg-query -W -f='${Package}\t${Version}\t${Architecture}\n' cloudflared
Cloudflare’s package currently places the owned binary at /usr/bin/cloudflared and creates /usr/local/bin/cloudflared as a symlink to it. The ownership query should therefore name cloudflared for the resolved path, while the package query should match the architecture selected for the repository.
Test Cloudflare Tunnel with a Quick Tunnel
A Quick Tunnel assigns a random trycloudflare.com address without requiring a Cloudflare account. It is useful for checking the package, local origin, DNS, and outbound tunnel connection before creating a permanent route. Cloudflare limits Quick Tunnels to development and testing; they have a 200 concurrent-request limit and do not support Server-Sent Events.
Use an existing local HTTP application when one is already running. For a self-contained test, install Python and create a new test directory. The guard stops instead of reusing or deleting a directory that already contains data:
sudo apt install python3
test_dir="$HOME/cloudflare-tunnel-test"
if [ -e "$test_dir" ]; then
printf 'Refusing to overwrite existing path: %s\n' "$test_dir" >&2
false
else
install -m 0700 -d "$test_dir"
printf '%s\n' 'Cloudflare Tunnel reached this Ubuntu origin.' \
> "$test_dir/index.html"
fi
Continue only when the block creates the new directory. If it prints the refusal message, do not run the server or cleanup commands against that path; use an existing local service or choose another unused directory consistently throughout the test.
Start a temporary HTTP server bound only to the local loopback address. Leave this terminal open while testing:
python3 -m http.server 8080 \
--bind 127.0.0.1 \
--directory "$HOME/cloudflare-tunnel-test"
Open a second terminal and confirm the local origin responds before involving Cloudflare:
curl -fsS http://127.0.0.1:8080/
The response should print Cloudflare Tunnel reached this Ubuntu origin. Start the Quick Tunnel against that exact URL:
cloudflared tunnel --url http://127.0.0.1:8080
The foreground process prints a temporary HTTPS address ending in trycloudflare.com. Open that address in a browser and confirm the test message appears. Press Ctrl+C in the tunnel terminal to remove the public endpoint, then stop the Python server with Ctrl+C in its terminal.
Quick Tunnels do not start when
~/.cloudflared/config.yamlis present. Ifcloudflaredreports that conflict, preserve the existing configuration and use the persistent tunnel workflow below, or temporarily move the file and restore it immediately after this test.
Remove the test directory when it is no longer needed:
rm -rf -- "$HOME/cloudflare-tunnel-test"
Create a Persistent Cloudflare Tunnel on Ubuntu
A production tunnel is remotely managed from Cloudflare and runs as a systemd service on Ubuntu. Before creating one, make sure the local application works without Cloudflare and decide which hostname will publish it.
- A Cloudflare account and a domain using Cloudflare DNS are required for a persistent public hostname.
- The origin service must be reachable from the Ubuntu host running
cloudflared. For a basic web origin, install and verify Nginx on Ubuntu before creating the route. - The host must be able to make outbound connections to Cloudflare on TCP or UDP port
7844. No inbound tunnel port needs to be opened.
Create the Tunnel and Install Its Service
Cloudflare’s current remotely managed tunnel workflow generates the secret token and the service command:
- Sign in to the Cloudflare dashboard and open Networking > Tunnels.
- Select Create a tunnel, enter a descriptive name for this Ubuntu host or application, then select Create Tunnel.
- Choose Linux and the architecture matching
dpkg --print-architecture. - Copy the generated command beginning with
sudo cloudflared service installand run the complete line in a private terminal on Ubuntu. - Wait for the connector to appear, then select Continue.
The generated tunnel token is a secret. Anyone holding it can start a connector for the tunnel. In an interactive Bash session, run
set +o historybefore pasting the tokenized command andset -o historyafterwards so the command is not retained in the current shell history. The installer still receives the token as a process argument, so run it only from a trusted administrative session where other local users cannot inspect privileged processes. Do not place the token in article comments, screenshots, support tickets, or shared terminal output.
The current Linux service installer parses the token, writes it to /etc/cloudflared/token with mode 0600, creates and enables cloudflared.service, then starts the connector. Confirm the token file remains readable only by root without displaying its contents:
sudo stat -c '%A %a %U:%G %n' /etc/cloudflared/token
Expected permissions are -rw------- 600 root:root. Cloudflare supports one cloudflared system service per host, but that one tunnel can publish several applications through separate routes.
Verify the cloudflared Service
Inspect the unit state without dumping its full configuration:
sudo systemctl show cloudflared.service \
--property=LoadState,ActiveState,SubState,UnitFileState \
--no-pager
Relevant fields should report:
LoadState=loaded ActiveState=active SubState=running UnitFileState=enabled
The dashboard should change the connector status to Healthy. An active systemd process proves that the service is running; the Healthy dashboard state separately proves that Cloudflare can see the connector.
Add a Public Hostname Route
Create a route only after the local origin works. In Networking > Tunnels, select the tunnel, open Routes, select Add route, then choose Published application.
- Enter the subdomain and domain that should reach the application.
- Select the matching service type, normally HTTP or HTTPS.
- Enter the origin URL. Use
http://127.0.0.1:8080for an application listening on loopback port 8080, orhttp://127.0.0.1:80for a default local Nginx listener. - Save the route and wait for the hostname to become available.
Use a private LAN address only when the application runs on another machine that the connector can reach. Traffic between cloudflared and that remote origin then crosses the local network, so use HTTPS or another protected transport when the LAN is not fully trusted.
A published hostname is Internet-accessible unless a Cloudflare Access policy protects it. Add an Access application for the self-hosted hostname before publishing an admin panel, dashboard, development service, or other private application.
Verify the Public Hostname
Collect the real hostname in the same Bash terminal you will use for the verification. Run this prompt by itself:
read -rp "Public tunnel hostname: " TUNNEL_HOSTNAME
Then reject an empty or malformed value and make a real HTTPS request:
if [[ "$TUNNEL_HOSTNAME" != *.* || "$TUNNEL_HOSTNAME" == *[[:space:]/:]* ]]; then
printf 'Enter a hostname only, without a scheme, path, port, or spaces.\n' >&2
false
else
curl -sS --max-time 15 -o /dev/null -w 'HTTP %{http_code}\n' \
"https://${TUNNEL_HOSTNAME}"
fi
A 2xx status confirms that the hostname returns a successful HTTPS response. Check the page content or an application-specific health endpoint when you must distinguish the intended origin from a cached or edge-generated response. A normal 3xx redirect can also be valid, including a redirect to a Cloudflare Access login page. A Cloudflare 502 response means the tunnel is connected but the connector cannot reach the configured origin.
Manage the Cloudflare Tunnel Service
Start, Stop, or Restart cloudflared
Use only the systemd command that matches the required service action:
sudo systemctl stop cloudflared.servicedisconnects the connector until it is started again.sudo systemctl start cloudflared.servicestarts a stopped connector.sudo systemctl restart cloudflared.servicereloads the connector by stopping and starting it.
Stopping or restarting the only connector interrupts every hostname using that tunnel. Add another connector or plan a maintenance window when the published service cannot tolerate a brief outage.
Read Cloudflare Tunnel Logs
Display the latest service messages without opening an interactive pager:
sudo journalctl -u cloudflared.service --no-pager -n 50
Follow new log entries only while reproducing a connection problem, then press Ctrl+C to stop the stream:
sudo journalctl -u cloudflared.service -f
The broader journalctl command guide covers time filters, boot selection, priority filters, and exporting focused log ranges.
Keep the Firewall Outbound-Only
Cloudflare Tunnel does not listen for public inbound connections on the Ubuntu host. Do not run sudo ufw allow 7844; that command creates an inbound allowance the connector does not need. UFW’s normal default permits outbound traffic, while tightly restricted egress policies should allow both TCP and UDP port 7844 to Cloudflare’s documented tunnel endpoints so automatic protocol fallback remains available.
Creating the tunnel does not remove an existing UFW allowance, cloud firewall rule, security-group rule, or router port forward. After the public hostname works, audit old ingress paths and close only the ones no longer required. The separate instructions for configuring UFW on Ubuntu cover numbered-rule review and deletion.
cloudflared can connect with QUIC over UDP or HTTP/2 over TCP and automatically falls back to the available protocol. The tunnel fails when both protocols are blocked. Use Cloudflare’s current firewall destination list instead of hardcoding Cloudflare IP ranges that can change over time.
Versions 2026.5.2 and later run DNS, port 7844, and management-API connectivity checks when a tunnel starts. When startup logs report a failed pre-check, compare the result with Cloudflare’s current connectivity pre-check guidance from the same Ubuntu host and network path.
Rotate a Tunnel Token
Rotate the token immediately if it was exposed. In the Cloudflare dashboard, open the tunnel and rotate its token, then remove the old local service:
sudo cloudflared service uninstall
Return to the dashboard’s connector setup and run the newly generated sudo cloudflared service install command. This replacement disconnects the existing connector between uninstall and reinstall, so use another healthy connector when continuous availability matters.
Update Cloudflare Tunnel on Ubuntu
Cloudflare’s package-manager update instructions require an APT installation to be updated through APT, not through cloudflared update. Upgrade the installed package and confirm the active command version:
sudo apt update
sudo apt install --only-upgrade cloudflared
cloudflared --version
A new Quick Tunnel uses the upgraded binary the next time it starts. When a persistent cloudflared.service is installed, restart it so the running connector loads the new binary:
sudo systemctl restart cloudflared.service
sudo systemctl is-active cloudflared.service
The last command should print active. Skip the systemd block when the host has only used foreground Quick Tunnels. A service restart interrupts the connections handled by that connector, including long-lived requests, until it reconnects to Cloudflare.
Remove Cloudflare Tunnel from Ubuntu
A Quick Tunnel creates no persistent dashboard route or DNS record. Skip the Cloudflare-side cleanup when the temporary trycloudflare.com endpoint was the only tunnel used on this host.
Removing the Ubuntu package does not delete a remotely managed tunnel, its public hostname routes, or Cloudflare DNS records. Remove the Cloudflare-side route before decommissioning a persistent connector:
- Open Networking > Tunnels, select the tunnel, open Routes, and delete the published application route that should stop working.
- Check the zone’s DNS records and remove a stale tunnel CNAME only if it remains after route deletion and no other service uses it.
- Delete the tunnel object only when no other route, replica, or replacement host will continue using it.
Uninstall the local system service before removing the binary. Skip this command when no persistent service was installed:
sudo cloudflared service uninstall
Purge the package after the service has been removed or after confirming the host only used Quick Tunnels:
sudo apt purge cloudflared
Clear the shell’s command cache, then verify that the APT package and the cloudflared command on the current PATH are gone:
hash -r
command -v cloudflared || true
dpkg-query -W -f='${db:Status-Abbrev} ${Package}\n' cloudflared 2>/dev/null || true
A complete purge prints no command path and no package line beginning with ii.
Remove the DEB822 source created by this installation, then refresh APT:
sudo rm -f /etc/apt/sources.list.d/cloudflared.sources
sudo apt update
The Cloudflare key can be shared by another Cloudflare package repository. Remove it only after confirming no remaining source file uses /usr/share/keyrings/cloudflare-main.gpg:
grep -RIn --include='*.list' --include='*.sources' \
"cloudflare-main.gpg" \
/etc/apt/sources.list /etc/apt/sources.list.d 2>/dev/null
If the command returns no remaining Cloudflare source after cloudflared.sources was removed, delete the unused key:
sudo rm -f /usr/share/keyrings/cloudflare-main.gpg
Troubleshoot Cloudflare Tunnel on Ubuntu
Fix Duplicate Cloudflare APT Sources
A Conflicting values set for option Signed-By error usually means an older cloudflared.list and the DEB822 source both describe the same repository with different key paths. Find every matching entry:
grep -RIn --include='*.list' --include='*.sources' \
"pkg.cloudflare.com/cloudflared" \
/etc/apt/sources.list /etc/apt/sources.list.d 2>/dev/null
Keep one source definition. When /etc/apt/sources.list.d/cloudflared.list is the duplicate legacy entry and the DEB822 file matches the installation above, remove the old file and refresh APT:
sudo rm -f /etc/apt/sources.list.d/cloudflared.list
sudo apt update
Do not remove a differently named source until its contents prove it points to the same Cloudflare repository. Cloudflare rotated its package-signing key in October 2025 and retired the previous key in April 2026, so a NO_PUBKEY or invalid-signature error requires repeating the current key installation and confirming the Signed-By path matches exactly. Do not bypass the error with trusted=yes or disabled signature checks.
Fix an Existing cloudflared Service or Error 1033
The service installer refuses to create a second cloudflared system service on the same machine. Add another published route to the existing tunnel when it is healthy. Uninstall the current service only when you intend to disconnect its routes and replace its token or tunnel.
Cloudflare error 1033 means no healthy connector is available for the requested tunnel. Check the tunnel status under Networking > Tunnels, then inspect the local unit and recent logs:
sudo systemctl show cloudflared.service \
--property=LoadState,ActiveState,SubState,UnitFileState \
--no-pager
sudo journalctl -u cloudflared.service --no-pager -n 50
If logs repeatedly show QUIC and HTTP/2 connection failures, confirm the network permits outbound TCP or UDP port 7844. Both protocols being blocked prevents the connector from becoming healthy. Restart the service after correcting the token or network problem:
sudo systemctl restart cloudflared.service
sudo systemctl is-active cloudflared.service
The second command should print active. Return to the dashboard and confirm the connector changes to Healthy before testing the public hostname again.
Fix a Cloudflare Tunnel 502 Error
A Cloudflare-branded 502 Bad Gateway page normally means the tunnel is healthy but the origin URL is wrong, stopped, listening on another address, or rejecting the connection. In the same Bash terminal you will use for testing, collect an unauthenticated origin URL without embedded credentials:
read -rp "Origin URL configured for the route: " ORIGIN_URL
Then validate the value and request that exact local or LAN endpoint:
if [[ ( "$ORIGIN_URL" != http://* && "$ORIGIN_URL" != https://* ) ||
"$ORIGIN_URL" == *[[:space:]]* || "$ORIGIN_URL" == *@* ]]; then
printf 'Enter an HTTP or HTTPS origin URL without spaces or credentials.\n' >&2
false
else
curl -v --max-time 10 "$ORIGIN_URL"
fi
If the request is refused, inspect the host’s TCP listeners and compare the route’s address and port with the application process:
sudo ss -ltnp
Make sure the route uses http:// for a plain HTTP origin and https:// for a TLS origin. A refused connection means the application is not listening at that address and port, while a timeout usually points to a firewall, routing, or remote-host problem. If localhost resolves to IPv6 while the application listens only on IPv4, save the route with 127.0.0.1 instead.
Fix an HTTPS Origin Certificate Error
An x509 error occurs after Cloudflare reaches an HTTPS origin but cannot validate the certificate name or trust chain. Use a certificate valid for the origin hostname, set the correct Origin Server Name in the route settings, or configure the appropriate certificate authority pool. Disabling TLS verification hides the certificate problem and should remain a temporary diagnostic rather than the permanent fix.
Conclusion
Cloudflare Tunnel is installed from the stable APT source, the connector can be managed by systemd, and a public hostname can reach the chosen local service without exposing an inbound server port. Keep cloudflared current through APT, inspect its journal after route or firewall changes, and protect private dashboards or admin tools with Cloudflare Access before publishing them.


Formatting tips for your comment
You can use basic HTML to format your comment. Useful tags currently allowed in published comments:
<code>command</code>command<strong>bold</strong><em>italic</em><a href="https://example.com">link</a><blockquote>quote</blockquote>