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:80Seeing 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.comThe 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 cloudflaredFor 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/cloudflaredCheck if it is installed properly:
cloudflared --versionHaving a version number is enough.
Log in to Cloudflare
Let the local cloudflared obtain Cloudflare account authorization:
cloudflared tunnel loginThe 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-webThe 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.jsonRemember 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.ymlExample 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:404Don'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 validateIf 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.comIf 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-webThen visit your domain:
https://app.example.comIf it opens, the main process is fine.
If it doesn't open, prioritize checking these points:
- Is the local service still alive?
- Is the
hostnamewritten 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 cloudflaredCheck the status:
sudo systemctl status cloudflaredView real-time logs:
journalctl -u cloudflared -fIf 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.