workerd: fetch to internal service failed with connection refused
Fixes workerd "connection refused" errors when a worker tries to fetch an internal service that is not reachable from the edge. Use it when code fetches a private IP, an internal hostname, or a service on your office or VPC network. Key trigger: the target is not on the public internet, and workers cannot reach private networks directly, so the fix is a service binding for worker-to-worker calls or a Cloudflare Tunnel for internal hosts.
TL;DR: Workers cannot dial private IPs or internal hostnames. If the target is another worker, call it through a service binding instead of HTTP. If it is a server inside your network, expose it with a Cloudflare Tunnel and fetch the tunnel hostname. There is no firewall rule that makes a private address reachable from the edge.
workerd: fetch to internal service failed with connection refused- Identify the target. Check the URL the worker fetches: a private-range IP, an internal-only hostname, or a host:port that only exists inside your network.
Expected: you can state plainly that the target is not on the public internet.
- If the target is another worker, replace the HTTP fetch with a service binding. Declare the binding in wrangler config and call the stub directly instead of fetching a URL.
Expected: worker-to-worker calls succeed with no network hop and no auth header juggling.
- If the target is a server inside your network, run cloudflared on that network to create a tunnel, and point the worker at the tunnel's public hostname.
Expected: the internal service answers through the tunnel hostname while staying private.
- Update the worker code to use the new target (binding stub or tunnel hostname) and redeploy.
Expected: the fetch succeeds and tail shows 200s instead of connection refused.
- Lock it down. Restrict the tunnel to the hostnames the worker needs, and require the service binding callee to validate the caller.
Expected: the internal service is reachable through exactly one path, not the open internet.
Use this when
- a worker fetches a private IP or internal hostname and gets connection refused
- the same URL works from inside your office or VPC but not from the worker
- you are migrating an internal API to be called from the edge
- worker-to-worker calls currently go over public HTTPS and should be direct
Not for this skill when
- the target is a public URL that refuses connections (that is the origin's firewall or the service being down)
- DNS fails to resolve the hostname (that is a DNS problem, not a private-network problem)
- the connection succeeds but returns 401 or 403 (that is auth, not reachability)
- you need the worker to reach a database (use Hyperdrive rather than a tunnel for databases)
Variant phrasings
- Cloudflare Worker cannot reach internal server
- worker fetch private IP connection refused
- "connection refused" fetching internal API from worker
- workerd fetch intranet host fails
- call internal service from Cloudflare Worker
Why it happens
Workers run on Cloudflare's edge, outside your network. Private IP ranges and internal DNS names simply do not exist out there, so the connection is refused at dial time. Service bindings solve worker-to-worker because they never leave Cloudflare's network, and tunnels solve the internal-server case by making your network dial out to Cloudflare and presenting the service on a public hostname through that outbound connection.
Edge cases
- A tunnel hostname is public. Anyone who guesses it can reach your service, so put access controls on the tunnel itself, not just obscurity.
- Service bindings only work worker-to-worker within the same account setup. Cross-account calls still need HTTPS with auth.
- Tunnel throughput adds a hop. For latency-sensitive paths, prefer moving the logic into a worker over tunneling back to your datacenter.
- Internal hostnames that resolve differently inside and outside your network are a classic trap. Always test the exact hostname the worker will fetch.
- Health checks from the worker through the tunnel count as subrequests like any other fetch. Budget them.
Provenance
Resolved from the public thread: https://vectle.com/posts/pst_QWqYekdutCu8-e3ao4OUqg