Skip to content

🐛 --protocol auto does not fall back to HTTP/2 when the QUIC handshake is rejected with CRYPTO_ERROR 0x178 (tls: no application protocol), and the UDP precheck reports it as healthy #1736

Description

@mashkovd

Describe the bug

On a network where the QUIC handshake to the tunnel edge is rejected during the TLS handshake (rather than blackholed), --protocol auto never falls back to HTTP/2. It retries QUIC indefinitely with exponential backoff, registers no connections, and the tunnel stays down.

Two distinct observations, in the same run and the same second:

  1. No fallback. Every dial fails with CRYPTO_ERROR 0x178 (remote): tls: no application protocol — TLS alert 120, no_application_protocol — and auto keeps retrying QUIC instead of switching transports.
  2. The startup precheck reports the opposite of reality. At the very moment those dials are failing, UDP Connectivity is reported status=pass with details="QUIC connection successful" for both regions, and the run concludes suggested_protocol=quic.

This looks like the second causes the first: the precheck's UDP component appears to test reachability rather than a real tunnel handshake, so the transport-selection logic is told QUIC is usable while it demonstrably is not.

Log

--protocol auto, cloudflared 2026.7.2. run_id elided, timestamps verbatim:

15:51:33Z INF Version 2026.7.2 (Checksum 809407c3d6e78787e2b14863ea6a1d4f31712186b5fd056815d093e29e8bff26)
15:51:33Z INF GOOS: darwin, GOVersion: go1.26.5, GoArch: amd64
15:51:34Z ERR Failed to dial a quic connection error="failed to dial to edge with quic: CRYPTO_ERROR 0x178 (remote): tls: no application protocol" connIndex=0 ip=198.41.200.33
15:51:34Z INF Retrying connection in up to 2s  connIndex=0 ip=198.41.200.33
15:51:35Z ERR Failed to dial a quic connection error="... tls: no application protocol" connIndex=0 ip=198.41.200.193
15:51:35Z INF Retrying connection in up to 4s  connIndex=0 ip=198.41.200.193
15:51:36Z ERR Failed to dial a quic connection error="... tls: no application protocol" connIndex=0 ip=198.41.200.23
15:51:36Z INF Retrying connection in up to 8s  connIndex=0 ip=198.41.200.23
15:51:40Z ERR Failed to dial a quic connection error="... tls: no application protocol" connIndex=0 ip=198.41.192.67
15:51:40Z INF Retrying connection in up to 16s connIndex=0 ip=198.41.192.67
15:51:40Z INF precheck component="DNS Resolution"  details="DNS Resolved successfully"   status=pass target=region1.v2.argotunnel.com
15:51:40Z INF precheck component="UDP Connectivity" details="QUIC connection successful" status=pass target=region1.v2.argotunnel.com
15:51:40Z INF precheck component="UDP Connectivity" details="QUIC connection successful" status=pass target=region2.v2.argotunnel.com
15:51:40Z INF precheck component="TCP Connectivity" details="HTTP/2 connection successful" status=pass target=region1.v2.argotunnel.com
15:51:40Z INF precheck complete hard_fail=false suggested_protocol=quic

The backoff kept growing (observed up to 1m4s). No Registered tunnel connection line ever appeared, over more than fifteen minutes, until the protocol was pinned to http2 by hand — after which all four connections registered within two seconds.

Expected behavior

After repeated QUIC dial failures, --protocol auto should fall back to HTTP/2, as documented in Run parameters. A TLS-level rejection of the QUIC handshake seems not to be classified as "QUIC is unusable" the way a timeout is.

Secondarily: UDP Connectivity should not report pass / QUIC connection successful on a path where the tunnel's own QUIC handshake is being rejected. As it stands the precheck is actively misleading — it was the reason we believed QUIC was viable here.

To Reproduce

  1. Be on a network that rejects the QUIC handshake to the edge rather than dropping the packets. Ours is a mobile carrier (see below) where UDP to 198.41.192.0/24 / 198.41.200.0/24 on 7844 is reachable but the handshake is refused.
  2. cloudflared tunnel --protocol auto run --token-file ...
  3. Observe repeated CRYPTO_ERROR 0x178, growing backoff, no fallback, no registered connections.

--protocol http2 on the same host and network works perfectly and has done for months.

How this differs from #749

#749 (closed, 2022) is the same expectation but a different trigger: there UDP was blocked and the symptom was i/o timeout. Here UDP reaches the edge and the failure happens inside the TLS handshake, which is presumably why it is classified differently — and why the precheck passes.

Environment and versions

  • OS: macOS 15.7.7
  • Architecture: x86_64
  • Version: 2026.7.2, reproduced on that build.
  • Also on 2026.8.3: --protocol quic produces the identical CRYPTO_ERROR 0x178. --protocol auto was not retested on 2026.8.3 — this is a production tunnel and each attempt takes the service down, so I did not want to run it again blind. Worth noting that on 2026.8.3 the precheck did, in a later run, correctly report UDP Connectivity ... QUIC connection failed with suggested_protocol=http2, so the precheck behaviour may already differ between the two builds.

Additional context

The network-side cause is almost certainly upstream of cloudflared and I am not asking you to fix it — QUIC from this uplink reaches Google, Facebook, Akamai and Vultr over HTTP/3 fine, but times out to Cloudflare (104.18.0.0/16, 1.1.1.0/24) and Fastly (151.101.0.0/16). Path MTU is 1500 to all of them.

The report is only about the two client-side behaviours: the missing fallback, and a precheck that reports a path as healthy while that same path is failing in the same second.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions