Turn your local machine into a web server with Cloudflare Tunnel

Comprehensive guide to turn your local machine into a web server with cloudflare tunnel

Cloudflare Tunnel

πŸ“‹ Table of Contents

What is Cloudflare Tunnel?

Cloudflare Tunnel (formerly Argo Tunnel) creates a secure, encrypted tunnel between your local machine and Cloudflare’s global network. It allows you to expose your localhost to the internet without:

How it works:

[Your Local App] β†’ [Cloudflared Client] β†’ [Cloudflare Edge] β†’ [Users Worldwide]

All connections are outbound-only from your machine, making it inherently more secure than traditional hosting methods.

Why Use Cloudflare Tunnel?

πŸ”’ Security Benefits

πŸš€ Practical Advantages

πŸ’° Cost Effective

Prerequisites

Before you begin, ensure you have:

Installation

macOS

Using Homebrew:

brew install cloudflare/cloudflare/cloudflared

Linux

Debian/Ubuntu:

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

RHEL/CentOS:

wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-x86_64.rpm
sudo rpm -i cloudflared-linux-x86_64.rpm

Arch Linux:

yay -S cloudflared

Windows

  1. Download the latest .exe from GitHub Releases
  2. Place it in a directory in your PATH (e.g., C:\Windows\System32)

Verify Installation

cloudflared --version

Expected output: cloudflared version 20XX.X.X

Quick Start

Step 1: Authenticate

cloudflared tunnel login

This opens your browser to authorize cloudflared with your Cloudflare account. Select your domain when prompted.

Step 2: Create a Tunnel

cloudflared tunnel create my-tunnel

Output:

Tunnel credentials written to /Users/username/.cloudflared/a7b3c4d5-e6f7-8901-2345-6789abcdef01.json
Created tunnel my-tunnel with id a7b3c4d5-e6f7-8901-2345-6789abcdef01

πŸ“ Save the tunnel ID - you’ll need it for configuration.

Example paths by OS:

Step 3: Create Configuration File

Create ~/.cloudflared/config.yml:

tunnel: my-tunnel
credentials-file: /home/username/.cloudflared/a7b3c4d5-e6f7-8901-2345-6789abcdef01.json

ingress:
  - hostname: app.yourdomain.com
    service: http://localhost:3000
  - service: http_status:404

Real-world example:

tunnel: my-blog-tunnel
credentials-file: /home/john/.cloudflared/a7b3c4d5-e6f7-8901-2345-6789abcdef01.json

ingress:
  - hostname: blog.jobayer.me
    service: http://localhost:4000
  - service: http_status:404

Replace:

Configuration file locations:

Step 4: Configure DNS

cloudflared tunnel route dns my-tunnel app.yourdomain.com

This creates a CNAME record pointing to your tunnel.

Real-world example:

cloudflared tunnel route dns my-blog-tunnel blog.jobayer.me

Output:

Created CNAME route for blog.jobayer.me
Route created successfully

What this does:

Verify in Cloudflare Dashboard:

  1. Go to your domain in Cloudflare Dashboard
  2. Click β€œDNS” in the sidebar
  3. You should see a CNAME record like:
    • Name: blog
    • Target: a7b3c4d5-e6f7-8901-2345-6789abcdef01.cfargotunnel.com

Step 5: Start the Tunnel

cloudflared tunnel run my-tunnel

Output:

2024-12-05T10:30:15Z INF Starting tunnel tunnelID=a7b3c4d5-e6f7-8901-2345-6789abcdef01
2024-12-05T10:30:16Z INF Connection registered connIndex=0 location=IAD
2024-12-05T10:30:17Z INF Connection registered connIndex=1 location=ATL
2024-12-05T10:30:18Z INF Connection registered connIndex=2 location=ORD
2024-12-05T10:30:19Z INF Connection registered connIndex=3 location=DFW

What this means:

πŸŽ‰ Your app is now live at https://blog.jobayer.me!

Testing your tunnel:

# From another terminal or device:
curl https://blog.jobayer.me

# Or open in browser:
# https://blog.jobayer.me

Real-world example: If you’re running a Next.js dev server on port 3000 locally, anyone can now access it at https://blog.jobayer.me with automatic HTTPS!

Keep it running:

Configuration

Basic Configuration Structure

tunnel: <tunnel-name>
credentials-file: <path-to-credentials>

ingress:
  - hostname: <your-hostname>
    service: <local-service>
  - service: http_status:404  # Catch-all rule (required)

Multiple Services

Route different subdomains to different local services:

tunnel: my-tunnel
credentials-file: /home/user/.cloudflared/a7b3c4d5-e6f7-8901-2345-6789abcdef01.json

ingress:
  # Frontend application
  - hostname: app.yourdomain.com
    service: http://localhost:3000
  
  # Backend API
  - hostname: api.yourdomain.com
    service: http://localhost:8080
  
  # Blog
  - hostname: blog.yourdomain.com
    service: http://localhost:4000
  
  # Admin panel
  - hostname: admin.yourdomain.com
    service: http://localhost:5000
  
  # Catch-all (required)
  - service: http_status:404

Real-world example - Full Stack Application:

tunnel: fullstack-dev
credentials-file: /home/john/.cloudflared/a7b3c4d5-e6f7-8901-2345-6789abcdef01.json

ingress:
  # React frontend (running on Vite dev server)
  - hostname: app.jobayer.me
    service: http://localhost:5173
  
  # Node.js/Express API
  - hostname: api.jobayer.me
    service: http://localhost:3000
  
  # PostgreSQL admin (pgAdmin)
  - hostname: db.jobayer.me
    service: http://localhost:5050
  
  # Storybook component library
  - hostname: storybook.jobayer.me
    service: http://localhost:6006
  
  - service: http_status:404

DNS Setup: After creating this config, run these commands:

cloudflared tunnel route dns fullstack-dev app.jobayer.me
cloudflared tunnel route dns fullstack-dev api.jobayer.me
cloudflared tunnel route dns fullstack-dev db.jobayer.me
cloudflared tunnel route dns fullstack-dev storybook.jobayer.me

Result:

Path-Based Routing

Route different URL paths to different services:

ingress:
  # API routes
  - hostname: yourdomain.com
    path: /api/*
    service: http://localhost:8080
  
  # Admin routes
  - hostname: yourdomain.com
    path: /admin/*
    service: http://localhost:5000
  
  # Default routes
  - hostname: yourdomain.com
    service: http://localhost:3000
  
  - service: http_status:404

Real-world example - Microservices on same domain:

tunnel: microservices
credentials-file: /home/john/.cloudflared/a7b3c4d5-e6f7-8901-2345-6789abcdef01.json

ingress:
  # Authentication service
  - hostname: jobayer.me
    path: /auth/*
    service: http://localhost:4000
  
  # Payment processing
  - hostname: jobayer.me
    path: /payments/*
    service: http://localhost:4001
  
  # User management
  - hostname: jobayer.me
    path: /users/*
    service: http://localhost:4002
  
  # Analytics dashboard
  - hostname: jobayer.me
    path: /analytics/*
    service: http://localhost:4003
  
  # Main frontend application (catch-all for other paths)
  - hostname: jobayer.me
    service: http://localhost:3000
  
  - service: http_status:404

URL routing result:

Custom Headers

Add custom headers to origin requests:

ingress:
  - hostname: app.yourdomain.com
    service: http://localhost:3000
    originRequest:
      httpHostHeader: localhost
      customHeaders:
        X-Custom-Header: "value"

Advanced Usage

Running as a System Service

Keep your tunnel running persistently, even after reboots.

Linux (systemd)

sudo cloudflared service install
sudo systemctl start cloudflared
sudo systemctl enable cloudflared

Check status:

sudo systemctl status cloudflared

View logs:

sudo journalctl -u cloudflared -f

macOS (launchd)

sudo cloudflared service install
sudo launchctl load /Library/LaunchDaemons/com.cloudflare.cloudflared.plist

Windows (Service)

Run as Administrator:

cloudflared service install
sc start cloudflared

Load Balancing

Distribute traffic across multiple instances:

ingress:
  - hostname: app.yourdomain.com
    service: http://localhost:3000
    originRequest:
      connectTimeout: 10s
  - hostname: app.yourdomain.com
    service: http://localhost:3001
  - service: http_status:404

Logging Configuration

Enable detailed logging:

tunnel: my-tunnel
credentials-file: /path/to/credentials.json
loglevel: debug
logfile: /var/log/cloudflared.log

ingress:
  - hostname: app.yourdomain.com
    service: http://localhost:3000
  - service: http_status:404

Log levels: debug, info, warn, error, fatal

Use Cases

1. Personal Blog/Portfolio

Scenario: Host a Hugo, Jekyll, or Next.js site from your laptop.

ingress:
  - hostname: blog.yourdomain.com
    service: http://localhost:1313  # Hugo default port
  - service: http_status:404

Real-world example - Hugo Blog:

tunnel: my-blog
credentials-file: /home/sarah/.cloudflared/a7b3c4d5-e6f7-8901-2345-6789abcdef01.json

ingress:
  - hostname: blog.jobayer.me
    service: http://localhost:1313
  - service: http_status:404

Setup steps:

# 1. Start your Hugo dev server
hugo server -D

# 2. In another terminal, create tunnel route
cloudflared tunnel route dns my-blog blog.jobayer.me

# 3. Run the tunnel
cloudflared tunnel run my-blog

Result: Your Hugo blog running on http://localhost:1313 is now accessible at https://blog.jobayer.me

Other static site generators:

# Jekyll (port 4000)
- hostname: blog.yourdomain.com
  service: http://localhost:4000

# Next.js (port 3000)
- hostname: portfolio.yourdomain.com
  service: http://localhost:3000

# Gatsby (port 8000)
- hostname: site.yourdomain.com
  service: http://localhost:8000

Benefits:

2. Development & Staging

Scenario: Share work-in-progress with clients or team members.

ingress:
  - hostname: dev.yourdomain.com
    service: http://localhost:3000
  - hostname: staging.yourdomain.com
    service: http://localhost:4000
  - service: http_status:404

Real-world example - Agency workflow:

tunnel: client-demos
credentials-file: /home/alex/.cloudflared/a7b3c4d5-e6f7-8901-2345-6789abcdef01.json

ingress:
  # Client A's project
  - hostname: clienta-dev.myagency.com
    service: http://localhost:3001
  
  # Client B's project
  - hostname: clientb-dev.myagency.com
    service: http://localhost:3002
  
  # Client C's project
  - hostname: clientc-dev.myagency.com
    service: http://localhost:3003
  
  - service: http_status:404

Setup multiple client demos:

# Route all domains
cloudflared tunnel route dns client-demos clienta-dev.myagency.com
cloudflared tunnel route dns client-demos clientb-dev.myagency.com
cloudflared tunnel route dns client-demos clientc-dev.myagency.com

# Start all dev servers
cd ~/projects/clienta && npm run dev &  # port 3001
cd ~/projects/clientb && npm run dev &  # port 3002
cd ~/projects/clientc && npm run dev &  # port 3003

# Run tunnel
cloudflared tunnel run client-demos

Share with clients:

Hi Client A,

Here's the latest version of your website:
https://clienta-dev.myagency.com

Let me know your feedback!

Update in real-time: When you save changes in your code editor, clients see updates immediately after page refresh!

Benefits:

3. Webhook Testing

Scenario: Test Stripe, GitHub, or other webhook integrations.

ingress:
  - hostname: webhooks.yourdomain.com
    service: http://localhost:8080
  - service: http_status:404

Real-world example - Stripe payment testing:

tunnel: stripe-webhooks
credentials-file: /home/mike/.cloudflared/a7b3c4d5-e6f7-8901-2345-6789abcdef01.json

ingress:
  - hostname: webhooks.mikestore.com
    service: http://localhost:4000
  - service: http_status:404

Express.js webhook handler:

// server.js
const express = require('express');
const app = express();

app.post('/webhooks/stripe', express.raw({type: 'application/json'}), (req, res) => {
  const sig = req.headers['stripe-signature'];
  const event = req.body;
  
  console.log('Received Stripe webhook:', event.type);
  
  switch (event.type) {
    case 'payment_intent.succeeded':
      console.log('Payment succeeded:', event.data.object);
      break;
    case 'payment_intent.payment_failed':
      console.log('Payment failed:', event.data.object);
      break;
  }
  
  res.json({received: true});
});

app.listen(4000);

Setup steps:

# 1. Create DNS route
cloudflared tunnel route dns stripe-webhooks webhooks.mikestore.com

# 2. Start your server
node server.js

# 3. Run tunnel
cloudflared tunnel run stripe-webhooks

# 4. Configure in Stripe Dashboard
# Webhook URL: https://webhooks.mikestore.com/webhooks/stripe

Test it:

# Create a test payment in Stripe Dashboard
# Watch your terminal for webhook logs:

Received Stripe webhook: payment_intent.succeeded
Payment succeeded: { id: 'pi_123abc', amount: 1000, ... }

Other webhook examples:

ingress:
  # GitHub webhooks
  - hostname: github.yourdomain.com
    path: /webhook
    service: http://localhost:3000
  
  # Twilio webhooks
  - hostname: twilio.yourdomain.com
    service: http://localhost:4000
  
  # PayPal IPN
  - hostname: paypal.yourdomain.com
    service: http://localhost:5000
  
  - service: http_status:404

Benefits:

4. Home Automation

Scenario: Access Home Assistant, Plex, or other home services remotely.

ingress:
  - hostname: home.yourdomain.com
    service: http://localhost:8123  # Home Assistant
  - hostname: media.yourdomain.com
    service: http://localhost:32400  # Plex
  - service: http_status:404

Real-world example - Complete smart home setup:

tunnel: smart-home
credentials-file: /home/chris/.cloudflared/a7b3c4d5-e6f7-8901-2345-6789abcdef01.json

ingress:
  # Home Assistant
  - hostname: home.jobayer.me
    service: http://localhost:8123
  
  # Plex Media Server
  - hostname: plex.jobayer.me
    service: http://localhost:32400
  
  # Pi-hole (ad blocking)
  - hostname: pihole.jobayer.me
    service: http://localhost:80
  
  # Synology NAS
  - hostname: nas.jobayer.me
    service: http://localhost:5000
  
  # Security cameras (Frigate)
  - hostname: cameras.jobayer.me
    service: http://localhost:5001
  
  - service: http_status:404

Setup DNS routes:

cloudflared tunnel route dns smart-home home.jobayer.me
cloudflared tunnel route dns smart-home plex.jobayer.me
cloudflared tunnel route dns smart-home pihole.jobayer.me
cloudflared tunnel route dns smart-home nas.jobayer.me
cloudflared tunnel route dns smart-home cameras.jobayer.me

Run as system service (Raspberry Pi):

sudo cloudflared service install
sudo systemctl start cloudflared
sudo systemctl enable cloudflared

Access from anywhere:

Mobile app integration: Configure Home Assistant mobile app to use https://home.jobayer.me - now you can control your home from anywhere!

Example - Control lights from work:

1. Open https://home.jobayer.me on your phone
2. Click "Living Room Lights"
3. Turn off (you forgot to turn them off!)

Benefits:

5. API Development

Scenario: Expose local API for mobile app development.

ingress:
  - hostname: api.yourdomain.com
    service: http://localhost:8080
    originRequest:
      connectTimeout: 30s
  - service: http_status:404

Real-world example - Mobile app backend:

tunnel: mobile-api
credentials-file: /home/dev/.cloudflared/a7b3c4d5-e6f7-8901-2345-6789abcdef01.json

ingress:
  # REST API
  - hostname: api.jobayer.me
    service: http://localhost:3000
    originRequest:
      connectTimeout: 30s
  
  # GraphQL API
  - hostname: graphql.jobayer.me
    service: http://localhost:4000
  
  # API documentation (Swagger)
  - hostname: docs.jobayer.me
    service: http://localhost:8080
  
  - service: http_status:404

Example Node.js/Express API:

// server.js
const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors());
app.use(express.json());

// API endpoints
app.get('/api/users', (req, res) => {
  res.json([
    { id: 1, name: 'John Doe' },
    { id: 2, name: 'Jane Smith' }
  ]);
});

app.post('/api/users', (req, res) => {
  console.log('Creating user:', req.body);
  res.json({ id: 3, ...req.body });
});

app.get('/api/health', (req, res) => {
  res.json({ status: 'ok', timestamp: Date.now() });
});

app.listen(3000, () => {
  console.log('API running on port 3000');
});

Setup:

# 1. Start API server
node server.js

# 2. Create DNS route
cloudflared tunnel route dns mobile-api api.jobayer.me

# 3. Run tunnel
cloudflared tunnel run mobile-api

Mobile app configuration (React Native):

// config.js
const API_BASE_URL = 'https://api.jobayer.me';

export const fetchUsers = async () => {
  const response = await fetch(`${API_BASE_URL}/api/users`);
  return response.json();
};

export const createUser = async (userData) => {
  const response = await fetch(`${API_BASE_URL}/api/users`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(userData)
  });
  return response.json();
};

Test from mobile device:

# Your phone can now access:
https://api.jobayer.me/api/users
https://api.jobayer.me/api/health

Development workflow:

  1. Make changes to API code
  2. Save file (nodemon auto-restarts)
  3. Test immediately on mobile device
  4. See changes in real-time!

Example - Testing on physical iPhone:

// In React Native app
fetch('https://api.jobayer.me/api/users')
  .then(res => res.json())
  .then(users => console.log('Users:', users));

// Output in debugger:
// Users: [{id: 1, name: 'John Doe'}, {id: 2, name: 'Jane Smith'}]

Benefits:

Troubleshooting

Tunnel Won’t Start

Problem: Error starting tunnel

Solutions:

  1. Check config syntax:

    cloudflared tunnel info my-tunnel
    
  2. Verify credentials file:

    ls ~/.cloudflared/
    
  3. Test local service:

    curl http://localhost:3000
    
  4. Check for port conflicts:

    lsof -i :3000  # macOS/Linux
    netstat -ano | findstr :3000  # Windows
    

Connection Timeouts

Problem: Tunnel connects but requests timeout

Solutions:

  1. Verify service is running:

    netstat -tuln | grep 3000
    
  2. Check firewall settings:

    sudo ufw status  # Linux
    
  3. Test with curl:

    curl -v http://localhost:3000
    
  4. Increase timeout:

    originRequest:
      connectTimeout: 60s
    

DNS Not Resolving

Problem: Domain doesn’t resolve to tunnel

Solutions:

  1. Check DNS records:

    • Go to Cloudflare Dashboard β†’ DNS
    • Verify CNAME record exists
  2. Wait for propagation:

    dig app.yourdomain.com
    nslookup app.yourdomain.com
    
  3. Force HTTPS:

    • Access via https:// not http://
  4. Clear DNS cache:

    # macOS
    sudo dscacheutil -flushcache
    
    # Linux
    sudo systemd-resolve --flush-caches
    
    # Windows
    ipconfig /flushdns
    

Certificate Errors

Problem: SSL/TLS certificate errors

Solutions:

  1. Wait for certificate provisioning (can take 5-10 minutes)

  2. Verify Cloudflare SSL mode:

    • Cloudflare Dashboard β†’ SSL/TLS β†’ Overview
    • Set to β€œFull” or β€œFull (strict)”
  3. For self-signed certs:

    originRequest:
      noTLSVerify: true
    

High Latency

Problem: Slow response times

Solutions:

  1. Check Cloudflare region:

    cloudflared tunnel info my-tunnel
    
  2. Monitor local resources:

    top  # or htop
    
  3. Enable compression:

    originRequest:
      disableChunkedEncoding: false
    
  4. Optimize application performance

Tunnel Keeps Disconnecting

Problem: Tunnel drops connection frequently

Solutions:

  1. Check logs:

    cloudflared tunnel run my-tunnel --loglevel debug
    
  2. Verify network stability

  3. Increase grace period:

    grace-period: 30s
    
  4. Check for updates:

    cloudflared update
    

Best Practices

Security

βœ… Use strong authentication

originRequest:
  access:
    required: true

βœ… Enable audit logging

logfile: /var/log/cloudflared.log
loglevel: info

βœ… Regularly update cloudflared

cloudflared update

βœ… Implement rate limiting in your application

βœ… Use environment variables for sensitive data

Performance

⚑ Enable HTTP/2

originRequest:
  http2Origin: true

⚑ Optimize application before exposing

⚑ Use caching where appropriate

⚑ Monitor resource usage

⚑ Consider CDN settings in Cloudflare Dashboard

Reliability

πŸ”„ Run as system service for automatic restart

πŸ”„ Monitor uptime with health checks

πŸ”„ Keep backups of configuration files

πŸ”„ Document your setup for team members

πŸ”„ Test failover scenarios

Organization

πŸ“ Use descriptive tunnel names

cloudflared tunnel create prod-web-server

πŸ“ Maintain separate configs for different environments

πŸ“ Version control your config files

πŸ“ Document DNS records and their purposes

πŸ“ Use consistent naming conventions

Managing Tunnels

List All Tunnels

cloudflared tunnel list

View Tunnel Details

cloudflared tunnel info my-tunnel

Delete a Tunnel

# Stop the tunnel first
cloudflared tunnel cleanup my-tunnel

# Delete the tunnel
cloudflared tunnel delete my-tunnel

# Remove DNS record
# (manually in Cloudflare Dashboard)

Rotate Credentials

cloudflared tunnel token my-tunnel

Update Tunnel Configuration

  1. Edit ~/.cloudflared/config.yml
  2. Restart the tunnel:
    sudo systemctl restart cloudflared
    

Additional Resources

Looking for a Full-Stack JavaScript Developer?

If you're hiring for a full-stack JavaScript developer position and found this article helpful, I'd love to contribute to your team! I specialize in modern JavaScript technologies including Node.js, NestJS, Angular, PostgreSQL, Docker, and Kubernetes, with extensive experience in RxJS, TypeScript, and building scalable web applications.

Let's connect:

  • πŸ“§ Email: jobayer6735@gmail.com
  • Schedule a meeting
  • πŸ’Ό View my portfolio and other technical articles on this site

I'm passionate about writing clean, maintainable code and sharing knowledge with the developer community. If your company values technical excellence and continuous learning, I'd be excited to discuss how I can help your team succeed.

Jobayer

© 2025 Jobayer Ahmed. All rights are reserved.