If you have ever needed to show a client a demo running on localhost:3000, test a webhook from Stripe, or access your home server while traveling, you know the struggle.

Traditionally, you had two bad choices: punching holes in your firewall (port forwarding) which exposes your network to the entire internet, or using tools like ngrok which give you random, temporary URLs that change every time you restart.

Enter Cloudflare Tunnel (formerly Argo Tunnel). It allows you to expose local services securely without opening a single inbound port. In this guide, we will set up a production-ready tunnel that is secure, fast, and free.


1. How It Works (The Architecture)

The magic of Cloudflare Tunnel is that it flips the traditional hosting model:

  • Old Way (Inbound): You open Port 80 on your router. Traffic comes in to your network. This is risky.
  • Cloudflare Way (Outbound): You run a lightweight daemon (cloudflared) on your machine. It creates an encrypted tunnel outbound to Cloudflare's edge network.

Because the connection is outbound, you do not need to touch your firewall configuration.

2. Why Developers Should Switch

  • It is Free: Part of the Zero Trust Free plan (up to 50 users).
  • Permanent Domains: No more xy123.ngrok.io. Use dev.yourdomain.com.
  • DDoS Protection: Attackers hit Cloudflare's massive network, not your home router.
  • Bypass CGNAT: Works perfectly on Starlink, LTE, and restricted ISP networks.

3. Hands-On Guide: Setting It Up

Step 1: Install the Cloudflared Daemon

Select your operating system below to see the installation commands:

Open PowerShell as Administrator and run:

winget install Cloudflare.cloudflared

Alternatively, download the .exe installer from GitHub.

The easiest way is using Homebrew:

brew install cloudflare/cloudflare/cloudflared

For Debian/Ubuntu based systems:


curl -L --output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared.deb

If you prefer running it in a container:

docker pull cloudflare/cloudflared:latest

Step 2: The "Quick Tunnel" (Instant Test)

Before configuring domains, let's verify it works. Ensure you have a local web server running (e.g., on port 8000). Run this command:

cloudflared tunnel --url localhost:8000

The terminal will output a random URL like https://funny-name.trycloudflare.com. Open that link—you are now live on the internet!

Step 3: The Production Setup (Named Tunnels)

For a permanent domain (e.g., app.mysite.com), follow these steps:

  1. Login: This opens a browser window to authorize your domain.
    cloudflared tunnel login
  2. Create the Tunnel: Give it a name (e.g., "home-server").
    cloudflared tunnel create home-server
    Copy the Tunnel ID (UUID) from the output.
  3. Route DNS: Point your subdomain to the tunnel.
    cloudflared tunnel route dns home-server app.mysite.com
  4. Run It:
    cloudflared tunnel run home-server
Pro Tip: For long-term use, create a config.yml file and install cloudflared as a system service so it starts automatically on reboot.

4. Security: Adding Zero Trust (The "Login" Screen)

Right now, your app is public. If you are hosting a private admin panel, you don't want the world to see it.

You can add an authentication layer without changing your app's code using Cloudflare Access:

  1. Go to the Zero Trust Dashboard.
  2. Navigate to Access > Applications > Add an Application.
  3. Select "Self-hosted".
  4. Enter your subdomain (e.g., app.mysite.com).
  5. Create a Policy: "Allow emails ending in @mycompany.com" or "Specific User: me@gmail.com".

Now, when anyone visits your URL, they are blocked by a Cloudflare login screen unless they authenticate. Your server is safe.

Conclusion

Cloudflare Tunnel is a massive upgrade over traditional VPNs and port forwarding. It is safer, easier to manage, and free for most developer use cases.

Official Resources:

Bonus: Making it Permanent (Configuration)

Running commands manually is great for testing, but for a stable "set and forget" environment, you should use a config.yml file. This allows you to define multiple routes and run the tunnel as a background service.

Create a file named config.yml in your .cloudflared directory and paste the configuration matching your platform below.

Linux / macOS
tunnel: 8e5558c4-1234-5678-9abc-000000000000 credentials-file: /root/.cloudflared/8e5558c4-1234-5678-9abc-000000000000.json ingress: # Route to local web server - hostname: app.example.com service: http://localhost:3000 # Route to SSH (Browser rendering) - hostname: ssh.example.com service: ssh://localhost:22 # Catch-all rule (Required) - service: http_status:404
Windows Service
tunnel: 8e5558c4-1234-5678-9abc-000000000000 # Note: Double backslashes are required for Windows paths credentials-file: C:\\Windows\\System32\\config\\systemprofile\\.cloudflared\\8e5558c4-1234-5678-9abc-000000000000.json ingress: - hostname: win-app.example.com service: http://localhost:8080 # RDP Access (Remote Desktop) - hostname: rdp.example.com service: rdp://localhost:3389 - service: http_status:404
Docker / Containers
tunnel: 8e5558c4-1234-5678-9abc-000000000000 credentials-file: /etc/cloudflared/creds.json ingress: # Route to another container by name - hostname: db-admin.example.com service: http://phpmyadmin:80 - hostname: blog.example.com service: http://wordpress:80 - service: http_status:404

Note: Replace the UUIDs (8e55...) with your actual Tunnel ID, which you can find by running cloudflared tunnel list.