grpc/grpc-go · error
extproc: failed to create channel to the external processor…
Error message
extproc: failed to create channel to the external processor server %q: %v
What it means
Returned by getOrCreateExtProcChannel (ext_proc.go:312) when iextproc.CreateExtProcChannel fails to dial the external processor server for the given TargetURI. The interceptor cannot be built, so BuildClientInterceptor returns an error and the RPC fails before reaching the dataplane unless failure_mode_allow semantics apply at a higher layer.
Solutions
- From the client pod, verify reachability: grpcurl or nc to the ext_proc server target URI/port.
- Check the ext_proc server is deployed and its Service resolves (kubectl get endpoints, nslookup).
- Confirm channel credentials and CA in the grpc_service match what the ext_proc server presents.
- Correct the TargetURI in the xDS grpc_service (use dns:/// or xds:/// scheme as intended) and republish.
- If dial failures are transient, ensure the client retries/resends the RPC so a fresh interceptor build redials.
Example fix
// before: unreachable target in xDS config
grpc_service { target_uri: "ext-proc:50000" } // wrong port / no DNS
// after: correct, resolvable target
grpc_service { target_uri: "dns:///ext-proc.extproc-system.svc.cluster.local:443" } Defensive patterns
Strategy: retry
Validate before calling
// Pre-flight: dial the ext_proc target before relying on it, then close.
func preflightExtProcTarget(ctx context.Context, target string) error {
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
cc, err := grpc.DialContext(ctx, target, grpc.WithBlock(), grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return fmt.Errorf("ext_proc target %q unreachable: %w", target, err)
}
cc.Close()
return nil
} Try / catch
// In the gRPC client: a failed BuildClientInterceptor surfaces as an RPC error.
// Retry the RPC so the channel redials; pair with failure_mode_allow for resilience.
for attempt := 0; attempt < 3; attempt++ {
err := conn.Invoke(ctx, method, req, resp)
if err == nil { break }
st, _ := status.FromError(err)
if st.Code() != codes.Internal || !strings.Contains(st.Message(), "failed to create channel") {
break
}
time.Sleep(backoff(attempt))
} Prevention
- Run a readiness/liveness probe against the ext_proc server in the same namespace.
- Verify DNS and network policies from the client pod to the ext_proc target before enabling the filter.
- Match the grpc_service credentials/CA to the ext_proc server's presented identity.
- Use a stable Service DNS name (dns:///ext-proc.ns.svc:443) rather than a pod IP.
When it happens
Trigger: Triggered at client-interceptor build time when dialing server.TargetURI fails: DNS lookup error, TCP/TLS handshake failure, no route to host, bad authority, unsupported channel credentials, or the ext_proc server is down.
Common situations: Ext_proc deployment not running or scaled to zero, wrong service DNS name in the xDS config, mTLS/CA mismatch between client and ext_proc server, network policy/firewall blocking the port, or a target URI scheme the resolver does not support.
Related errors
- failed to create a stream to external processor
- external processor returned invalid body mutation in body…
- external processor returned unexpected status
- external processor returned unexpected status
- external processor sent response body after response…
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/79a73044db67cfe4.
Report an issue: GitHub.
Appendix: source
Thrown at internal/xds/httpfilter/extproc/ext_proc.go:312
// one if it doesn't exist.
func (cf *clientFilter) getOrCreateExtProcChannel(server xdsresource.GRPCServiceConfig) (*grpcsync.RefCounted[v3procservicegrpc.ExternalProcessorClient], error) {
// Create the grpcServiceKey.
key := grpcServiceKey{
targetURI: server.TargetURI,
channelCredentials: server.ChannelCredentials,
callCredentials: server.CallCredentials,
}
// If the channel for the key is present in the map and its refcount is
// greater than 0, increment the refcount and return the channel.
if rc := cf.getProcChannel(key); rc != nil {
return rc, nil
}
// Create the external processor channel without holding the lock.
cc, cancel, err := iextproc.CreateExtProcChannel(server)
if err != nil {
return nil, fmt.Errorf("extproc: failed to create channel to the external processor server %q: %v", server.TargetURI, err)
}
client := v3procservicegrpc.NewExternalProcessorClient(cc)
// Create a new refcounted client. The onZero cleanup function will remove the
// client from the map and close the underlying channel.
var rc *grpcsync.RefCounted[v3procservicegrpc.ExternalProcessorClient]
rc = grpcsync.NewRefCounted(&client, func() {
cf.removeProcChannel(key, rc)
cancel()
})
// Double-check if another goroutine created and stored a channel for this
// key while we were unlocked.
if existing := cf.storeProcChannel(key, rc); existing != rc {
rc.Decrement()
return existing, nil
}
return rc, nilView on GitHub (pinned to 0c51461d27)