Short answers
How do I use an authenticated proxy with Axios?
For this Node.js recipe, pass HttpsProxyAgent as httpsAgent and set proxy: false. The agent opens the CONNECT tunnel and supplies proxy authentication. The Axios HTTP adapter then sends the HTTPS request through it.
Does this configuration work in browser Axios?
No. Browser JavaScript does not control the browser’s network proxy through httpsAgent. Run this example in a Node.js process. Keep the proxy password on that server, outside frontend bundles.
Why is an HTTPS request using an http:// proxy URL?
The URL describes the connection to the gateway. CONNECT establishes a tunnel, then TLS protects the request to the HTTPS destination. The HTTP gateway connection and its proxy authentication are not themselves protected by TLS.
Use the Node HTTP adapter and one agent
This recipe was checked with Node.js 22.22.0, Axios 1.20.0 and https-proxy-agent 9.1.0 on 1 October 2026. Install the pinned packages in your application and retain its lockfile. These versions identify the tested combination; review updates before adopting a different release.
npm install --save-exact axios@1.20.0 https-proxy-agent@9.1.0
node --version
npm ls axios https-proxy-agentAxios documents custom agents, proxy settings and adapter selection in its request configuration reference. The HTTPS proxy agent documentation describes its CONNECT implementation. Select the HTTP adapter explicitly: changing to the fetch adapter changes the transport configuration you need.
Open the connection builder, choose HTTP, and copy the complete username for the desired pool, country and rotation. Supply it as PROXY_USER and the proxy password as PROXY_PASSWORD through your runtime secret configuration. Optional PROXY_SERVER contains the HTTP gateway address only. Do not paste passwords into source files, command arguments or shell history.
Make one bounded HTTPS echo request
Save this as axios-check.mjs, then run node axios-check.mjs. It makes one GET, follows no redirects and accepts only a successful echo containing a valid IP address. Its output is deliberately limited to the result or a short failure category.
import axios from 'axios';
import { HttpsProxyAgent } from 'https-proxy-agent';
import { isIP } from 'node:net';
function required(name) {
const value = process.env[name];
if (!value) throw new Error('Missing runtime setting');
return value;
}
async function main() {
const proxy = new URL(process.env.PROXY_SERVER ?? 'http://gw.portproof.org:7000');
if (proxy.protocol !== 'http:' || !proxy.hostname || proxy.username ||
proxy.password || proxy.pathname !== '/' || proxy.search || proxy.hash) {
throw new Error('Invalid gateway URL');
}
const username = required('PROXY_USER');
if (username.includes(':')) throw new Error('Invalid Basic username');
proxy.username = encodeURIComponent(username);
proxy.password = encodeURIComponent(required('PROXY_PASSWORD'));
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 20_000);
const agent = new HttpsProxyAgent(proxy, {
keepAlive: false, maxSockets: 1, signal: controller.signal,
});
let connectStatus;
agent.on('proxyConnect', (response) => { connectStatus = response.statusCode; });
try {
const response = await axios.get('https://api.portproof.org/v1/echo-ip', {
adapter: 'http', httpsAgent: agent, proxy: false,
signal: controller.signal, timeout: 10_000,
maxRedirects: 0, maxContentLength: 65_536, maxBodyLength: 0,
responseType: 'text', transformResponse: [(body) => body],
validateStatus: () => true,
});
if (connectStatus !== 200) {
throw new Error('Proxy CONNECT status ' + connectStatus);
}
if (response.status !== 200) {
throw new Error('Destination HTTP status ' + response.status);
}
const payload = JSON.parse(response.data);
if (!payload || typeof payload.ip !== 'string' || !isIP(payload.ip)) {
throw new Error('Invalid echo JSON');
}
console.log('HTTP', response.status, 'exit', payload.ip);
} catch (error) {
if (connectStatus && connectStatus !== 200) {
console.error('Proxy CONNECT status', connectStatus);
} else if (controller.signal.aborted) {
console.error('Request deadline exceeded');
} else if (axios.isAxiosError(error)) {
console.error('Request failed:', error.code ?? 'AXIOS_ERROR');
} else {
console.error(error instanceof SyntaxError ? 'Invalid echo JSON' : error.message);
}
process.exitCode = 1;
} finally {
clearTimeout(timer);
controller.abort();
agent.destroy();
}
}
main().catch(() => {
console.error('Check gateway and runtime credential settings');
process.exitCode = 1;
});The agent emits the CONNECT response separately from the HTTPS response. Recording that status avoids calling a gateway refusal a destination failure. A 200 tunnel response only permits TLS to begin; the destination must still pass certificate verification and return the expected JSON.
Encode credentials once and keep them scoped
The script builds a URL from the gateway address and encodes the two credential components independently. Password characters such as @, /, ? and % therefore retain their meaning as password characters. Supply the original password, not a value you already percent-encoded. A colon is rejected in the username because Basic authentication uses it as the username/password separator.
Do not put Proxy-Authorization in Axios request headers or use its top-level auth setting for the gateway password. Those fields concern the destination request. The agent creates the proxy authentication header for CONNECT. Its URL and internal configuration contain credentials, so keep agent debug logging disabled and never print a whole Axios error, request or config object.
Here proxy: false disables Axios’s own proxy selection while the explicit agent still provides the route. Conflicting HTTPS_PROXY or NO_PROXY values did not replace this agent in the fixture. For a deployment that intentionally uses environment routing, configure that policy separately and test it; do not infer the route from an environment variable’s presence.
Bound time, response size and cleanup
The request has a ten-second Axios timeout and a twenty-second abort timer. The same abort signal reaches the agent’s gateway socket, including a pending CONNECT handshake. Cleanup aborts that owned socket and destroys the agent. The local stalled and trickling cases exited within the fixture deadline; event-loop scheduling means these settings are budgets, not exact wall-clock guarantees.
The response limit is 64 KiB of buffered response data. It is separate from elapsed time, request-body limits and billable traffic: protocol overhead and network buffering still exist. The example permits no request body. For a workload with uploads or larger results, set appropriate limits deliberately and count all attempted requests when estimating traffic requirements.
Certificate verification remains enabled. A managed environment can provide an approved additional CA file through Node’s NODE_EXTRA_CA_CERTS before starting the process. Fix an incorrect hostname or trust chain rather than disabling verification. A 407 CONNECT response occurs before the destination TLS connection and requires a different investigation.
Read the stage before changing settings
- Proxy CONNECT status 407
- Check the complete generated username, current proxy password and remaining traffic. Use the authentication checklist.
- Proxy CONNECT status 502 or 503
- The gateway did not establish the tunnel. Compare a small echo check with the authorised target and follow the CONNECT guide.
- Destination HTTP status 403 or 429
- The destination answered after the tunnel opened. Follow its access policy or rate limit; repeated credential changes do not resolve that response.
- ECONNABORTED or request deadline exceeded
- A configured time budget ended the check. Inspect worker connectivity and whether the failure happens before headers or during the body. If the gateway connection itself is refused or never completes, use the connection-refused guide.
- ERR_BAD_RESPONSE or invalid echo JSON
- Check the response-size limit and expected payload. Do not accept a 200 status alone as evidence of a useful result.
What the local checks establish
The published snippet passed thirteen loopback cases with synthetic credentials and a test certificate. They covered punctuation, 407 and 502 tunnel responses, a destination 403, a redirect, invalid JSON and IP data, the exact byte limit and an oversized body, an untrusted certificate, and three timeout stages. The destination received no proxy authentication header; output contained no fixture credentials.
The fixture substituted local endpoints and shorter timeouts. It did not contact production, benchmark a pool, establish country targeting or test SOCKS5. After your echo succeeds, verify one authorised application result. For a service, scope a reusable client to a stable proxy configuration and bound queued work. This one-request agent’s shutdown must not cancel unrelated jobs. See sessions and connection reuse or the Node fetch recipe for the corresponding workflow.
What is not allowed
Use proxies for authorised QA, monitoring and research within the destination’s terms and the acceptable-use policy.
Read next
Node.js fetch proxy setup with Undici
An explicit HTTP proxy dispatcher with safe credential handling, bounded requests and response cleanup.
Read the guide
Fix 407 Proxy Authentication Required
Read the gateway code, check credentials and balance, then separate a rejected HTTPS tunnel from an HTTP response.
Read the guide