MagicTools
Developer ToolsAugust 5, 20266 views4 min read

Node.js Built-in Fetch Doesn't Use HTTPS_PROXY: The Second Pitfall When Connecting to Google API

1. Problem Description

In the previous article 'GA4 Data API Proxy Environment Timeout DEADLINE_EXCEEDED', I fixed the gRPC proxy. Following the same environment variable approach, I wrote a data fetching script for Google Search Console—GSC is a regular REST API, so using the built-in fetch is sufficient:

const res = await fetch(
  "https://searchconsole.googleapis.com/webmasters/v3/sites",
  { headers: { Authorization: `Bearer ${token}` } }
);

The performance when running:

TypeError: fetch failed
  cause: ConnectTimeoutError: Connect Timeout Error

In the same terminal:

  • curl -x http://127.0.0.1:7897 https://searchconsole.googleapis.com works normally
  • HTTPS_PROXY is exported in both uppercase and lowercase
  • The grpc_proxy fixed for GA4 in the previous article is still there

Environment variables are all set, but fetch doesn't read any of them.

2. Environment

Item Details
OS macOS (Darwin 23.6)
Node.js 22.18.0 (built-in fetch provided by undici)
Target API Google Search Console (REST)
Proxy Local Clash mixed port 127.0.0.1:7897

3. Root Cause: This Is Not a Bug, It's a Design

The built-in fetch in Node.js from v18 onwards is based on undici. The default behavior of undici is: completely ignore environment variables like HTTP_PROXY / HTTPS_PROXY / NO_PROXY, and if you need a proxy, you must explicitly configure the dispatcher in the code.

This is contrary to most people's intuition, because almost all common tools read these variables:

Tool / Library Reads Environment Proxy Variables?
curl / wget ✅ Yes
axios ✅ Yes
gaxios (HTTP layer of the googleapis ecosystem) ✅ Reads uppercase HTTPS_PROXY and lowercase https_proxy
request / node-fetch (agent libraries) Generally supported by ecosystem solutions
Node built-in fetch (undici) ❌ No by default

The typical pitfall path is: first use curl to verify that the proxy is fine, then write the fetch version, and then question your life over the timeout—the success of curl is precisely the misleading factor, as it and fetch are not in the same proxy discovery mechanism.

4. Three Fix Paths

If your scenario is similar to mine, calling Google APIs, the smoothest solution is to let the authentication library handle the HTTP layer as well:

import { GoogleAuth } from "google-auth-library";

const auth = new GoogleAuth({
  scopes: ["https://www.googleapis.com/auth/webmasters.readonly"],
});
const client = await auth.getClient();

// client.request uses gaxios under the hood, which honors HTTPS_PROXY
const res = await client.request({
  url: "https://searchconsole.googleapis.com/webmasters/v3/sites",
});

This solution has two additional benefits:

  • Token refresh is automatically handled, no need to manually construct the Authorization header
  • If you are already using official SDKs like @google-analytics/data, google-auth-library is their transitive dependency, no additional installation is needed, and it also avoids the large googleapis package

gaxios has the same strict requirements for proxy addresses as grpc-js—the value must have the http:// prefix; writing 127.0.0.1:7897 directly will throw Invalid URL.

Path 2 (General): undici ProxyAgent

For scenarios where you want to use fetch but not with Google APIs, explicitly configure the dispatcher for undici:

import { ProxyAgent, setGlobalDispatcher } from "undici";

setGlobalDispatcher(new ProxyAgent("http://127.0.0.1:7897"));

// every fetch in this process now goes through the proxy
const res = await fetch("https://example.com");

If you don't want to affect globally, pass it per request:

const res = await fetch(url, {
  dispatcher: new ProxyAgent("http://127.0.0.1:7897"),
});

(dispatcher is an undici extended option, and type assertions may be needed in TypeScript.)

Path 3 (Newer Node): Experimental Environment Variable Switch

Newer versions of Node.js provide an experimental NODE_USE_ENV_PROXY=1 to make the built-in fetch respect environment proxy variables. If your Node version supports this and you can accept experimental features, this is the solution with minimal changes—but in projects locked to Node 22 (like mine), the first two paths are more stable. Test it on the target version before use, and don't write experimental switches into production scripts.

5. Combined with the Previous Article: Quick Reference for Google API Proxy Configuration

With the same Google credentials, the transport layers of different APIs are completely different, so the proxy configurations are also completely different:

API Transport Layer Proxy Configuration
GA4 Data API (@google-analytics/data) gRPC Lowercase grpc_proxy=http://... (uppercase HTTPS_PROXY is ineffective)
Search Console / most googleapis REST (gaxios) HTTPS_PROXY=http://... (recognized in both cases)
Writing fetch yourself undici All environment variables are ineffective; must configure ProxyAgent in code

My actual approach is to uniformly prefix the script with two variables to cover the first two cases:

HTTPS_PROXY=http://127.0.0.1:7897 grpc_proxy=http://127.0.0.1:7897 node script.js

6. Troubleshooting Mantras

  • curl works ≠ your code will work: curl has its own proxy discovery mechanism, which cannot verify the behavior of the HTTP library you use
  • When changing libraries, first ask: Does this library read environment proxy variables? Which one? What case?—directly browse the source code in node_modules; it's faster than checking documentation and won't be outdated
  • When Node built-in fetch times out and the environment has a proxy, the first reaction should be that undici doesn't read environment variables; don't waste time checking DNS and firewalls

Related Articles

GA4 Data API Proxy Environment Timeout DEADLINE_EXCEEDED: gRPC Doesn't Recognize Uppercase HTTPS_PROXY

GA4 Data API times out with DEADLINE_EXCEEDED after 60 seconds in a proxy environment, while curl works fine with the same proxy. The root cause is that the SDK uses gRPC under the hood, and grpc-js only reads lowercase grpc_proxy / https_proxy, making uppercase HTTPS_PROXY invisible to it. This article goes through the error phenomenon to source code layer by layer, providing a paired fix.

developerAug 5, 20264 min
5

Cloudflare Wallets Explained: An Identity and a Stablecoin Wallet for AI Agents — cloudflare.pay Handles Are Live

On August 4, 2026, Cloudflare announced Cloudflare Wallets and cloudflare.pay during Agents Week: a human-readable, stable identity for AI agents plus a stablecoin wallet with owner-defined spending guardrails. This guide covers the problem it solves, how Account Wallets and Virtual Wallets split responsibilities, how x402 micropayments complete the two-sided agentic market, and the one thing you can do today — reserve your wallet handle.

cloudflareai-agent+4
ai-tutorialsAug 5, 20266 min
3

Next.js Full-Site Hydration Mismatch: The Root Cause is GTM-Loaded AdSense, Not Your Code

The Next.js site console randomly shows 'A tree hydrated but some attributes didn't match,' and the first page in cold cache is clean while subsequent pages always report it? The root cause is not in your components, but in the AdSense auto-ads loaded by GTM injecting scripts into the head before React hydration, colliding with the inline GTM script. This article explains the timing puzzle, a one-line fix, and diagnostic techniques using Playwright to capture the full error diff.

developerAug 5, 20264 min
7

GitHub Actions Fails Quickly with Connection Reset: The Cross-Border Pitfall When Overseas Runners Pull Alibaba Cloud ACR

GitHub Actions build fails two seconds after starting at load metadata, reporting failed to fetch oauth token: connection reset by peer, with the error pointing to the FROM line in the Dockerfile. Don't change the code—this is an intermittent interruption in the cross-border link from overseas runners to the Alibaba Cloud ACR authentication service. This article provides criteria, a workflow with automatic retries, and a structural solution.

developerAug 5, 20264 min
5

Published by MagicTools