← Back to DevBytes

Troubleshooting Cloudflare Tunnel: Common Issues and Fixes

Introduction to Cloudflare Tunnel

Cloudflare Tunnel (formerly known as Argo Tunnel) is a secure way to expose your local services, internal applications, or private infrastructure to the internet without opening inbound ports on your firewall. Instead of allowing public traffic directly to your server, the cloudflared daemon establishes an outbound connection to Cloudflare's edge network, and Cloudflare proxies requests back through that persistent tunnel.

This architecture dramatically reduces your attack surface, but it also introduces a unique set of failure modes. When a tunnel breaks, the symptoms can be subtle: intermittent 502 errors, DNS records that point nowhere, or a service that works locally but is unreachable from the public internet. This tutorial walks through the most common Cloudflare Tunnel issues and provides concrete, tested fixes for each one.

Why Troubleshooting Matters

Because Cloudflare Tunnel reverses the traditional client-server direction, debugging requires a different mental model. You are no longer tracing a packet from the outside in; you are tracing it from the inside out, then back in again through Cloudflare's edge. Understanding where the chain breaks — DNS, edge routing, the tunnel connection, the local config, or the origin service — is the key to fast resolution.

Prerequisites and Setup

Before diving into troubleshooting, ensure you have a working baseline. You will need:

Install cloudflared on a Linux machine with the following commands:

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

# Authenticate
cloudflared tunnel login

# Create a tunnel
cloudflared tunnel create my-tunnel

This generates a credentials file at ~/.cloudflared/<TUNNEL_UUID>.json. Keep this file safe — without it, your tunnel cannot authenticate.

Issue 1: Tunnel Not Connecting to Cloudflare Edge

The most fundamental problem is when cloudflared cannot establish its outbound connection to Cloudflare's edge servers. Symptoms include the tunnel showing as "inactive" in the Cloudflare dashboard and all routed URLs returning 530 errors.

Diagnosing the Connection

Run cloudflared in the foreground with verbose logging to see exactly what is happening:

cloudflared tunnel --config ~/.cloudflared/config.yml run my-tunnel --loglevel debug

Look for lines mentioning connection attempts to region1.v2.argotunnel.com or similar edge hostnames. If you see repeated "dial tcp" errors, the daemon cannot reach Cloudflare's edge at all.

Common Causes and Fixes

Firewall blocking outbound traffic: Cloudflare Tunnel requires outbound access on ports 7844 (TCP and UDP) to Cloudflare's edge IPs. Many corporate firewalls restrict high-numbered ports. Verify connectivity:

# Test connectivity to Cloudflare edge
nc -vz region1.v2.argotunnel.com 7844

# If nc is unavailable, use curl with a timeout
curl -v --connect-timeout 5 https://region1.v2.argotunnel.com:7844

If the connection fails, add an outbound allow rule for port 7844 to Cloudflare's published IP ranges. You can fetch the current ranges programmatically:

curl -s https://www.cloudflare.com/ips-v4 | grep -E '^[0-9]'

DNS resolution failures: If your machine cannot resolve Cloudflare's edge hostnames, the tunnel cannot connect. Check DNS:

dig region1.v2.argotunnel.com +short
nslookup region1.v2.argotunnel.com

If resolution fails, switch to a public resolver like 1.1.1.1 or ensure your internal DNS forwarders are functioning.

Outdated cloudflared binary: Cloudflare periodically deprecates older protocol versions. Check your version and upgrade if needed:

cloudflared --version
cloudflared update

Issue 2: DNS Records Not Routing to the Tunnel

A tunnel can be connected and healthy, but if DNS records do not point to the correct tunnel ID, traffic will never reach your origin. This is one of the most common misconfigurations.

Checking DNS Configuration

Cloudflare Tunnel uses a special CNAME record format that routes traffic through the tunnel. The CNAME should point to <TUNNEL_UUID>.cfargotunnel.com. Verify this in the Cloudflare dashboard under DNS, or query it directly:

dig app.example.com +short
# Expected output: something like
# abc123def456.cfargotunnel.com.
# 104.x.x.x

If the CNAME is missing or points to the wrong UUID, fix it with the cloudflared CLI:

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

This command creates or updates the CNAME record automatically. You can also manage routes via the Cloudflare dashboard under Access > Tunnels > your tunnel > Public Hostnames.

Conflicting DNS Records

If a hostname already has an A or AAAA record, the CNAME will not take effect because CNAMEs cannot coexist with other record types at the same name. Delete conflicting records before routing through the tunnel:

# Using the Cloudflare API to list records for a zone
curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?name=app.example.com" \
  -H "Authorization: Bearer $CF_API_TOKEN" \
  -H "Content-Type: application/json" | jq '.result[] | {id, type, name, content}'

Once you identify conflicting A/AAAA records, delete them and re-run the route command.

Issue 3: 502 Bad Gateway Errors

A 502 error means Cloudflare's edge successfully routed the request through the tunnel, but cloudflared could not get a valid response from your origin service. The tunnel itself is fine; the problem is between cloudflared and your local application.

Verifying the Origin Service

First, confirm your service is actually running and listening on the expected port:

# Check if the service is listening
ss -tlnp | grep 8080
# or
netstat -tlnp | grep 8080

# Test locally
curl -v http://localhost:8080/

If curl succeeds locally but the tunnel returns 502, the issue is likely in your config.yml ingress rules.

Checking Ingress Rules

The config.yml file defines how cloudflared maps incoming hostnames to local services. A common mistake is a mismatch between the hostname in the ingress rule and the actual request, or pointing to the wrong local URL. Here is a correct example:

tunnel: my-tunnel
credentials-file: /root/.cloudflared/abc123-def456.json

ingress:
  - hostname: app.example.com
    service: http://localhost:8080
  - hostname: api.example.com
    service: http://localhost:3000
  - service: http_status:404

Every ingress configuration must end with a catch-all rule (service: http_status:404 or similar). Without it, cloudflared will refuse to start. Validate your config before running:

cloudflared tunnel ingress validate

Protocol Mismatches

If your origin service uses HTTPS with a self-signed certificate, cloudflared will reject the connection by default. You have two options: disable TLS verification for that specific service, or use HTTP for the local connection. To disable verification:

ingress:
  - hostname: app.example.com
    service: https://localhost:8443
    originRequest:
      noTLSVerify: true
  - service: http_status:404

Note that disabling TLS verification should only be done for local, trusted services. For production, use a proper certificate or stick with plain HTTP on localhost.

Issue 4: Tunnel Randomly Disconnects

Intermittent disconnections are frustrating because they often happen under load or at irregular intervals. The tunnel appears healthy, then drops, then reconnects. This usually points to resource exhaustion, network instability, or systemd configuration issues.

Checking Systemd Service Configuration

If you installed cloudflared as a systemd service, the default configuration may not restart the service aggressively enough. Inspect and improve the service file:

# View the current service file
systemctl cat cloudflared

# Create an override to improve restart behavior
sudo systemctl edit cloudflared

Add the following override to ensure the service restarts quickly and does not get killed during high memory pressure:

[Service]
Restart=always
RestartSec=5
LimitNOFILE=65536

[Unit]
StartLimitIntervalSec=0

Reload and restart:

sudo systemctl daemon-reload
sudo systemctl restart cloudflared

Memory and Connection Limits

Under heavy traffic, cloudflared can exhaust file descriptors or memory. Monitor resource usage:

# Check current resource usage
systemctl status cloudflared

# Monitor in real time
watch -n 2 'ps -p $(pgrep cloudflared) -o pid,vsz,rss,%mem,%cpu,cmd'

# Check open file descriptors
ls /proc/$(pgrep cloudflared)/fd | wc -l

If file descriptor counts are climbing, increase the system-wide limit in /etc/security/limits.conf:

* soft nofile 65536
* hard nofile 65536

Network Instability

If your origin has an unreliable internet connection, the tunnel will drop whenever connectivity is lost. Cloudflare Tunnel supports multiple connections to different edge servers for redundancy. By default, cloudflared maintains four connections. You can verify this in the logs:

journalctl -u cloudflared --no-pager | grep "Registered tunnel connection"

You should see four "Registered tunnel connection" lines. If you see fewer, some connections are failing to establish. Check your network's packet loss and latency:

# Install mtr for combined traceroute/ping
sudo apt install mtr
mtr -T -P 7844 region1.v2.argotunnel.com

High packet loss on the path to Cloudflare's edge indicates an ISP or transit provider issue that you may need to escalate.

Issue 5: Access Policies Blocking Legitimate Users

Cloudflare Access integrates with Tunnel to add authentication layers. Misconfigured access policies can block users who should have access, or worse, leave sensitive applications exposed.

Reviewing Access Policies

Check your access applications in the Cloudflare dashboard under Zero Trust > Access > Applications. Each application should have at least one policy with explicit allow rules. Verify the policy logic with the API:

curl -s -X GET "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/access/apps" \
  -H "Authorization: Bearer $CF_API_TOKEN" \
  -H "Content-Type: application/json" | jq '.result[] | {name, domain, self_hosted_domains}'

Common mistakes include using "Block" policies where "Allow" was intended, or specifying email domains that do not match your organization. A correct policy for allowing all users from a specific email domain looks like this when created via API:

curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/access/apps/$APP_ID/policies" \
  -H "Authorization: Bearer $CF_API_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{
    "name": "Allow Company Users",
    "decision": "allow",
    "include": [
      {
        "email_domain": {
          "domain": "example.com"
        }
      }
    ]
  }'

Testing Access Policies

Use an incognito browser window or a different device to test access policies from the perspective of an unauthenticated user. If you are unexpectedly blocked, check the Access logs:

# Query recent access audit logs
curl -s -X GET "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/access/logs/access?since=24h" \
  -H "Authorization: Bearer $CF_API_TOKEN" \
  -H "Content-Type: application/json" | jq '.result[] | {user, action, app_domain, created_at}'

Look for "deny" actions to understand which policy is blocking the user and why.

Issue 6: WebSocket Connections Failing

Many modern applications (real-time dashboards, chat apps, terminal emulators) rely on WebSockets. Cloudflare Tunnel supports WebSockets, but certain configurations can break them.

Enabling WebSocket Support

WebSocket support is enabled by default in Cloudflare Tunnel, but you must ensure your ingress rule points to the correct protocol. For WebSocket-heavy applications, use http:// or https:// as the service URL — do not use tcp://, which bypasses HTTP processing:

ingress:
  - hostname: ws.example.com
    service: http://localhost:8080
    originRequest:
      http2Origin: false
  - service: http_status:404

The http2Origin: false setting can resolve WebSocket issues when the origin does not support HTTP/2 properly.

Timeout Configuration

Long-lived WebSocket connections may be terminated if they exceed default timeout values. Increase the connection timeout in your ingress rules:

ingress:
  - hostname: ws.example.com
    service: http://localhost:8080
    originRequest:
      connectTimeout: 30s
      tcpKeepAlive: 30s
      keepAliveTimeout: 90s
      keepAliveConnections: 100
  - service: http_status:404

Also check the Cloudflare dashboard under Network settings for your zone — ensure "WebSocket" is enabled at the zone level.

Issue 7: Performance and Latency Problems

Even when the tunnel works, users may experience high latency or slow throughput. This is often caused by suboptimal edge routing, compression overhead, or origin performance bottlenecks.

Diagnosing Latency

Measure the round-trip time from your origin to Cloudflare's edge:

# Measure latency to the nearest Cloudflare edge
curl -o /dev/null -s -w "DNS: %{time_namelookup}s\nConnect: %{time_connect}s\nTLS: %{time_appconnect}s\nTotal: %{time_total}s\n" https://app.example.com

Compare the "Connect" time to direct latency measurements. If the tunnel adds significant overhead, consider enabling connection pooling and adjusting the number of edge connections:

tunnel: my-tunnel
credentials-file: /root/.cloudflared/abc123-def456.json

# Increase edge connections for higher throughput
# Default is 4; increase for busy tunnels
edgeConnections: 8

ingress:
  - hostname: app.example.com
    service: http://localhost:8080
    originRequest:
      connectTimeout: 10s
      keepAliveConnections: 100
      keepAliveTimeout: 90s
  - service: http_status:404

Compression and Caching

Cloudflare automatically compresses responses at the edge, but if your origin also compresses, you may waste CPU cycles double-compressing. Disable origin compression and let Cloudflare handle it:

# In your nginx config, disable gzip for tunnel traffic
# since Cloudflare will compress at the edge
gzip off;

Enable caching for static assets in the Cloudflare dashboard or via Page Rules to reduce load on your origin entirely.

Best Practices for Reliable Tunnels

Beyond fixing specific issues, following these best practices will prevent most problems before they occur.

Use Infrastructure as Code

Manage your tunnel configuration with version-controlled YAML files rather than ad-hoc CLI commands. This makes changes reviewable and reproducible:

# config.yml - version controlled
tunnel: my-tunnel
credentials-file: /etc/cloudflared/credentials.json
metrics: 0.0.0.0:36500
loglevel: info
transport-loglevel: warn

ingress:
  - hostname: app.example.com
    service: http://localhost:8080
    originRequest:
      connectTimeout: 10s
      noTLSVerify: false
  - hostname: api.example.com
    service: http://localhost:3000
  - service: http_status:404

Enable Metrics and Monitoring

Cloudflare Tunnel exposes Prometheus-compatible metrics. Enable the metrics endpoint and scrape it with your monitoring stack:

# Enable metrics in config.yml
metrics: 0.0.0.0:36500

Then scrape the metrics endpoint:

curl -s http://localhost:36500/metrics | grep cloudflared

Key metrics to alert on include cloudflared_tunnel_server_locations (number of active edge connections), cloudflared_tunnel_total_requests, and cloudflared_tunnel_request_errors. Set up alerts for when active connections drop below 2, indicating potential connectivity problems.

Run Multiple Tunnels for High Availability

For production-critical services, run multiple cloudflared instances on different machines, all connected to the same tunnel. Cloudflare's edge will load-balance across them and failover automatically if one instance goes down:

# On server 1
cloudflared tunnel run my-tunnel

# On server 2 (same tunnel, same credentials)
cloudflared tunnel run my-tunnel

Both instances register with the edge, and Cloudflare distributes traffic across all active connections. This provides redundancy without any additional load balancer.

Secure Your Credentials

The credentials JSON file is effectively the keys to your tunnel. Store it with appropriate permissions and never commit it to version control:

# Set restrictive permissions
chmod 600 /etc/cloudflared/credentials.json
chown cloudflared:cloudflared /etc/cloudflared/credentials.json

# Use a secrets manager for deployment
# Example: retrieve from AWS Secrets Manager at deploy time
aws secretsmanager get-secret-value --secret-id cloudflared/credentials --query SecretString --output text > /etc/cloudflared/credentials.json

Keep cloudflared Updated

Cloudflare releases updates regularly with bug fixes, performance improvements, and security patches. Set up automatic updates or monitor for new releases:

# Check for updates manually
cloudflared update

# Or schedule periodic checks via cron
echo "0 3 * * 0 /usr/bin/cloudflared update --version" | sudo crontab -

Debugging Checklist

When a tunnel is not working, work through this checklist in order to quickly isolate the problem:

Conclusion

Cloudflare Tunnel is a powerful tool for securely exposing services without opening inbound ports, but its unique architecture means troubleshooting requires understanding the full request path from Cloudflare's edge through the tunnel to your origin. By methodically checking each layer — edge connectivity, DNS routing, ingress configuration, origin health, and access policies — you can quickly identify and resolve most issues. The key to long-term reliability is combining proactive monitoring with infrastructure-as-code practices: version-control your config, enable metrics, run multiple tunnel instances for redundancy, and keep cloudflared updated. With these practices in place, Cloudflare Tunnel can provide a robust, secure, and low-maintenance connection between your private infrastructure and the public internet.

— Ad —

Google AdSense will appear here after approval

← Back to all articles