Sometimes you want to expose a home computer, Raspberry Pi, or an intranet server to the public internet, but you don't have a public IP and tweaking the router is a hassle.

This is where Cloudflare Tunnel comes in handy. The logic is simple: your local machine actively connects to Cloudflare, and people outside access this tunnel via your domain name. You don't need to expose ports, and you don't have to care if your ISP provides a public IP.

Use Cases

  • Running a website at home and wanting to temporarily expose it to the external network.
  • Binding a domain to a NAS, panel, or test service.
  • Avoiding opening a bunch of ports on the router.
  • Wanting HTTPS but lacking a public IP.

Do not treat it as a universal reverse proxy. If you are running high-concurrency, high-traffic services, just use a proper server. Tunnel is more suited for personal services, test environments, and low-frequency access.

Prerequisites

Confirm these things first:

Item Description
Cloudflare Account The domain must already be hosted on Cloudflare
A local machine Linux is the easiest, Windows works too, but this guide focuses on Linux
A local service For example, http://127.0.0.1:80, http://127.0.0.1:8080
A domain name For example, app.example.com

First, test if your local service is alive:

curl -I http://127.0.0.1:80

Seeing a normal response like 200, 301, or 302 is fine. If you can't even access it locally, don't mess with Tunnel yet, otherwise troubleshooting will be very abstract.

Final Result

After finishing, the external network will access your domain:

https://app.example.com

The request will enter through Cloudflare and then be forwarded to the intranet service via the active Tunnel established by your local machine. You don't need port mapping on the router, nor do you need a public IP at home.

Install cloudflared

cloudflared is the client for Cloudflare Tunnel.

On macOS, you can just use Homebrew:

brew install cloudflared

For Linux, just download the binary file:

curl -L 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64' -o ./cloudflared
chmod +x ./cloudflared
sudo mv ./cloudflared /usr/local/bin/cloudflared

Check if it is installed properly:

cloudflared --version

Having a version number is enough.

Log in to Cloudflare

Let the local cloudflared obtain Cloudflare account authorization:

cloudflared tunnel login

The command will provide a link. Copy and open it in your browser, log in to Cloudflare, select the domain you want to use, and then authorize it.

After successful authorization, the local machine will generate a certificate file. Don't randomly delete this file; it will be needed later to create the tunnel.

Create a Tunnel

Give the tunnel a name, for example, home-web:

cloudflared tunnel create home-web

The output will contain two key pieces of information:

Created tunnel home-web with id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Tunnel credentials written to /root/.cloudflared/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.json

Remember these two:

  • Tunnel ID
  • credentials file path

These will need to be filled in the configuration file later.

Write the Configuration File

Create the configuration directory:

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

Example configuration:

tunnel: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
credentials-file: /root/.cloudflared/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.json

ingress:
  - hostname: app.example.com
    service: http://127.0.0.1:80

  - hostname: admin.example.com
    service: http://127.0.0.1:8080

  - service: http_status:404

Don't just copy the domain name here. Replace it with your own.

ingress is matched in order, so it's recommended to keep the last 404. Otherwise, it gets messy handling unmatched requests.

After writing the configuration, check it first:

cloudflared tunnel ingress validate

If it passes, continue.

Bind the Domain Name

Point the domain to this Tunnel:

cloudflared tunnel route dns home-web app.example.com
cloudflared tunnel route dns home-web admin.example.com

If a DNS record conflict is prompted, go to the Cloudflare dashboard, delete the old A, AAAA, and CNAME records, and execute it again.

Run Manually First

Don't rush to install the service, run it first to see:

cloudflared tunnel run home-web

Then visit your domain:

https://app.example.com

If it opens, the main process is fine.

If it doesn't open, prioritize checking these points:

  • Is the local service still alive?
  • Is the hostname written incorrectly?
  • Has the Cloudflare DNS been pointed to the Tunnel?
  • Is the server time completely off?
  • Is the configuration file path correct?

Set up Auto-Start on Boot

After confirming that running manually is fine, install it as a systemd service:

sudo cloudflared service install
sudo systemctl enable --now cloudflared

Check the status:

sudo systemctl status cloudflared

View real-time logs:

journalctl -u cloudflared -f

If you can see a successful connection in the logs and the domain is accessible, you're pretty much done.

Common Commands

Function Command
Log in to account cloudflared tunnel login
Create a tunnel cloudflared tunnel create <name>
View tunnels cloudflared tunnel list
Delete a tunnel cloudflared tunnel delete <name>
Bind a domain cloudflared tunnel route dns <name> <domain>
Validate config cloudflared tunnel ingress validate
Run in foreground cloudflared tunnel run <name>
View service sudo systemctl status cloudflared
View logs journalctl -u cloudflared -f

Some Security Advice

Tunnel does not mean you can leave everything unprotected.

If it's an admin panel, it's best to add another layer of login, or use Cloudflare Access to restrict access. Otherwise, even if the port isn't exposed, the service itself might still get scanned.

Simply put: treat anything accessible from the public network as a public service. Don't take chances.