Axios Proxy in Node.js: HTTP, HTTPS, SOCKS5
Sep 17, 2026 · Guides · 14 min read
To use axios with a proxy in Node.js, pass a proxy object with protocol, host, port, and an optional auth object to the request or to an axios instance. On axios 1.16.1 and later, the same setting works for HTTP and HTTPS targets: axios forwards plain HTTP requests and opens a CONNECT tunnel for HTTPS. For SOCKS5, use a routing agent such as socks-proxy-agent with proxy: false.
This guide is for Node.js developers who call APIs or collect data they are authorized to access and need the traffic to leave through a proxy. It uses Axios 1.21.0 with Node.js 22.22.0 or 24.22.1 and the Node.js HTTP adapter. Browser Axios and the fetch adapter use different networking paths and are outside this guide’s scope.
Quick answer
import axios from 'axios';
const response = await axios.get('https://example.com/', {
proxy: {
protocol: 'http',
host: 'PROXY_HOST',
port: 8000,
auth: { username: 'PROXY_USERNAME', password: 'PROXY_PASSWORD' },
},
timeout: 15000,
signal: AbortSignal.timeout(15000),
});
This snippet uses ES module syntax, so save it as a .mjs file or in a project with "type": "module". It also works in TypeScript. The longer examples below use CommonJS require.
protocoldescribes the connection to the proxy, not the scheme of the target URL. Use'http'for an ordinary HTTP proxy even when the target ishttps://, and'https'only when the proxy itself accepts TLS.- Axios also reads
HTTP_PROXY,HTTPS_PROXY, andNO_PROXYwhen noproxyoption is set. proxy: falseturns off axios’s own proxy selection for that request.- For SOCKS5, put a
SocksProxyAgentonhttpAgentandhttpsAgentand setproxy: false. - For an IP-allowlisted proxy, leave out the
authobject.
Before you start: versions and setup
Axios proxy support described here applies to server-side Node.js with axios’s HTTP adapter, which is the default in Node. Your proxy provider’s dashboard gives you the host, port, username, and password. The proxy password is usually separate from your dashboard login.

Install the packages used in this guide:
npm install [email protected] [email protected] [email protected]
npm ls axios https-proxy-agent socks-proxy-agent
npm ls axios prints the installed version. Use 1.21.0, the current release on npm as of October 28, 2026, or at least 1.20.0 for proxied traffic. Several published axios advisories involve proxies:
| Issue | Affected axios versions | Fixed in |
|---|---|---|
| HTTPS request data sent in cleartext to an HTTP proxy (v1.16.1 release notes) | 1.x before 1.16.1 | 1.16.1 |
CVE-2026-44486: a stale Proxy-Authorization header can be sent to a redirect target |
1.0.0 to 1.15.x, and 0.x before 0.32.0 | 1.16.0 and 0.32.0 |
GHSA-3pq3-5fj3-cg6v: requests with httpVersion: 2 skip the configured proxy |
1.13.0 and later, before 1.20.0 | 1.20.0 |
GHSA-44g4-m2mj-wpvx: CIDR entries in NO_PROXY are ignored |
1.15.0 and later, before 1.20.0 | 1.20.0 |
GHSA-mghh-pgcx-3jjj: denial of service (ReDoS) in NO_PROXY matching, reachable through redirects |
1.15.0 and later, before 1.20.0 | 1.20.0 |
Also check your lockfile for axios 1.14.1 and 0.30.4. Those two versions were published from a compromised maintainer account on March 31, 2026 and carried a malicious dependency, according to Snyk’s incident write-up.
Axios proxy authentication with an HTTP proxy
Axios proxy authentication with a username and password is the most common setup. Save this file as axios-proxy.cjs:
The examples below use PROXY_HOST, PROXY_PORT, PROXY_USERNAME, PROXY_PASSWORD, and TARGET_URL. Replace the placeholder values with your connection details. Save each later JavaScript example as a separate .cjs file and run it with node filename.cjs; the CA and IP-check examples additionally use ORIGIN_CA_FILE and IP_ECHO_URL.
const axios = require('axios');
const required = ['PROXY_HOST', 'PROXY_PORT', 'PROXY_USERNAME', 'PROXY_PASSWORD', 'TARGET_URL'];
for (const name of required) {
if (!process.env[name]) throw new Error('Set ' + name + ' before running this example.');
}
const port = Number(process.env.PROXY_PORT);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
throw new Error('PROXY_PORT must be an integer from 1 to 65535.');
}
const target = new URL(process.env.TARGET_URL);
if (!['http:', 'https:'].includes(target.protocol)) {
throw new Error('TARGET_URL must use http:// or https://.');
}
const proxy = {
protocol: 'http',
host: process.env.PROXY_HOST,
port: Number(process.env.PROXY_PORT),
auth: {
username: process.env.PROXY_USERNAME,
password: process.env.PROXY_PASSWORD,
},
};
axios
.get(process.env.TARGET_URL, { proxy, timeout: 15000, signal: AbortSignal.timeout(15000) })
.then((res) => console.log(res.status, res.data))
.catch((err) => {
console.error('Request failed:', err.response ? err.response.status : err.code);
process.exitCode = 1;
});
Set the connection details and run it. Keep deployed credentials in a secret store and avoid saving them in shell history. In Bash or zsh:
export PROXY_HOST='YOUR_PROXY_HOST'
export PROXY_PORT='YOUR_PROXY_PORT'
export PROXY_USERNAME='YOUR_PROXY_USERNAME'
export PROXY_PASSWORD='YOUR_PROXY_PASSWORD'
export TARGET_URL='https://YOUR_AUTHORIZED_TARGET/'
node axios-proxy.cjs
In PowerShell:
$env:PROXY_HOST = 'YOUR_PROXY_HOST'
$env:PROXY_PORT = 'YOUR_PROXY_PORT'
$env:PROXY_USERNAME = 'YOUR_PROXY_USERNAME'
$env:PROXY_PASSWORD = 'YOUR_PROXY_PASSWORD'
$env:TARGET_URL = 'https://YOUR_AUTHORIZED_TARGET/'
node axios-proxy.cjs
A working setup prints the expected status and response body from your target. For a controlled endpoint designed to return 200, check that both the status and expected content match. To confirm routing, look for a forwarded request for an http:// target or a CONNECT request for an https:// target in the proxy’s access log. A 407 response means “Proxy Authentication Required”; check credentials and the proxy’s authentication requirements.
The auth object takes the username and password as plain strings, including characters such as @, :, and #. Do not URL-encode auth.password. The axios request config documentation lists protocol, host, port, and auth as the proxy fields.
Set both timeout and an abort signal. A request timeout alone may not cover every connection phase, including a proxy that accepts TCP but stalls during CONNECT. signal: AbortSignal.timeout(15000) adds a separate cancellation deadline. Create a fresh signal for each request; an expired signal cannot be reused.
Axios HTTPS proxy: how HTTPS requests reach an HTTP proxy
An axios HTTPS request through an HTTP proxy uses a CONNECT tunnel on axios 1.16.1 and later. The proxy sees the target host and port, and TLS with the target site starts inside that tunnel, so the proxy does not see the decrypted request. Plain http:// targets are forwarded as ordinary requests, and the proxy sees the full URL.
Earlier 1.x releases used forward-proxy handling for HTTPS targets. The v1.16.1 release notes describe the fix for HTTPS request data sent in cleartext to an HTTP proxy. The version boundary matters when following older examples that recommend a separate tunnelling agent.

You do not need to install a separate routing package for this setup: the proxy option handles HTTP forwarding and HTTPS tunnelling.
If the target uses a certificate from a private or internal CA, the error appears inside the tunnel, for example DEPTH_ZERO_SELF_SIGNED_CERT or UNABLE_TO_VERIFY_LEAF_SIGNATURE. Supply that CA through a standard https.Agent and keep the proxy option for routing:
const axios = require('axios');
const fs = require('node:fs');
const https = require('node:https');
const originTlsAgent = new https.Agent({
ca: fs.readFileSync(process.env.ORIGIN_CA_FILE),
});
axios
.get(process.env.TARGET_URL, {
proxy: {
protocol: 'http',
host: process.env.PROXY_HOST,
port: Number(process.env.PROXY_PORT),
auth: {
username: process.env.PROXY_USERNAME,
password: process.env.PROXY_PASSWORD,
},
},
httpsAgent: originTlsAgent,
timeout: 15000,
signal: AbortSignal.timeout(15000),
})
.then((res) => console.log(res.status, res.data))
.catch((err) => {
console.error('Request failed:', err.response ? err.response.status : err.code);
process.exitCode = 1;
});
Set ORIGIN_CA_FILE to the PEM file of the CA you trust. A ca option replaces Node’s default trusted CAs for that agent, so use this agent only for internal targets, or pass ca: [...tls.rootCertificates, fs.readFileSync(process.env.ORIGIN_CA_FILE)] with require('node:tls') if the same agent also calls public sites. Node’s NODE_EXTRA_CA_CERTS environment variable adds a CA for the whole process instead. Do not set rejectUnauthorized: false to silence the error in production, because it turns off the check that you are talking to the intended server.
Use the HTTP/1.x adapter path for these proxy examples; do not add httpVersion: 2. Axios 1.20.0 introduced rejection of explicit proxy configuration on HTTP/2 requests. The axios request config documentation also explains that HTTP/2 ignores proxy environment variables. GHSA-3pq3-5fj3-cg6v documents how earlier affected HTTP/2 versions bypassed configured proxy controls.
Reuse proxy settings with an axios instance
An axios instance applies one proxy configuration to every request it sends. Any field set on a single request overrides the instance default for that request.
const axios = require('axios');
const client = axios.create({
proxy: {
protocol: 'http',
host: process.env.PROXY_HOST,
port: Number(process.env.PROXY_PORT),
auth: {
username: process.env.PROXY_USERNAME,
password: process.env.PROXY_PASSWORD,
},
},
timeout: 15000,
});
async function main() {
const viaProxy = await client.get(process.env.TARGET_URL, { signal: AbortSignal.timeout(15000) });
const direct = await client.get(process.env.TARGET_URL, { proxy: false, signal: AbortSignal.timeout(15000) });
console.log('instance:', viaProxy.status, 'override:', direct.status);
}
main().catch((err) => { console.error('Request failed:', err.code); process.exitCode = 1; });
The first request inherits the instance’s proxy configuration. The second disables Axios’s own proxy handling. With Axios 1.21.0 and the standard Node.js agents, it also avoids Node’s native environment-proxy settings; a custom agent can still choose its own route.
Use HTTP_PROXY, HTTPS_PROXY, and NO_PROXY
Axios proxy environment variables work without code changes. When a request has no proxy option, axios reads HTTP_PROXY for http:// targets and HTTPS_PROXY for https:// targets, in uppercase or lowercase, and skips the proxy for hosts listed in NO_PROXY.
export HTTPS_PROXY='http://PROXY_USERNAME:PROXY_PASSWORD@PROXY_HOST:PROXY_PORT'
export NO_PROXY='localhost,internal.example'
node your-app.cjs
Three configuration details matter here:
NO_PROXY=localhostexcludes localhost from environment-selected proxy routing. Axios 1.20.0 fixed support for CIDR entries such as10.0.0.0/8in its own proxy-exclusion handling.- Reserved characters in URL credentials need percent-encoding; unencoded delimiters can change URL parsing or produce
ERR_INVALID_URL. - Axios 1.21.0’s own environment-proxy parser reads URL credentials without percent-decoding them. If your password contains reserved characters, use the explicit
proxyobject with a plainauth.password, or a routing agent that decodes URL credentials. Node’s native environment-proxy support is a separate path, so do not assume every parser handles credentials identically.
To make a single request ignore these variables, set proxy: false:
const axios = require('axios');
axios
.get(process.env.TARGET_URL, { proxy: false, timeout: 15000, signal: AbortSignal.timeout(15000) })
.then((res) => console.log(res.status, res.data))
.catch((err) => { console.error('Request failed:', err.code); process.exitCode = 1; });
Node.js’s built-in HTTP and HTTPS agents support environment-based proxy routing from Node.js 22.21.0 and 24.5.0. Enable it at startup with NODE_USE_ENV_PROXY=1 or --use-env-proxy, as described in the Node.js HTTP proxy documentation. This is separate from fetch’s proxy support. On Axios 1.20.0, a native agent can still apply its proxy settings when proxy: false disables Axios’s own handling. Axios 1.21.0 adds direct-agent handling for this case. Confirm the route with access logs, especially if your application supplies custom agents.
Use https-proxy-agent instead of the proxy option
https-proxy-agent is a separate package that opens the CONNECT tunnel itself. It is useful when your code already passes agents around or when another library expects one. Give it the proxy URL with percent-encoded credentials, put it on httpsAgent, and set proxy: false so axios does not also apply its own proxy:
const axios = require('axios');
const { HttpsProxyAgent } = require('https-proxy-agent');
const user = encodeURIComponent(process.env.PROXY_USERNAME);
const pass = encodeURIComponent(process.env.PROXY_PASSWORD);
const agent = new HttpsProxyAgent(
`http://${user}:${pass}@${process.env.PROXY_HOST}:${process.env.PROXY_PORT}`
);
axios
.get(process.env.TARGET_URL, { httpsAgent: agent, proxy: false, timeout: 15000, signal: AbortSignal.timeout(15000) })
.then((res) => console.log(res.status, res.data))
.catch((err) => { console.error('Request failed:', err.code); process.exitCode = 1; });
This example uses https-proxy-agent 9.1.0. Percent-encode each credential before constructing the proxy URL; the agent decodes the URL credentials for proxy authentication.
Assigning HttpsProxyAgent only to httpsAgent routes https:// targets. This configuration does not set a proxy route for http:// targets, which use httpAgent. Keep proxy: false so Axios does not select a second proxy from the environment. If you need both target schemes through an HTTP proxy, Axios’s built-in proxy option provides one configuration for both.
Axios SOCKS5 proxy setup
An axios proxy for SOCKS5 needs an agent because the built-in proxy option supports HTTP-family proxies, not SOCKS. Setting protocol: 'socks5' on that option can produce a protocol-mismatch error. Use socks-proxy-agent instead:
const axios = require('axios');
const { SocksProxyAgent } = require('socks-proxy-agent');
const user = encodeURIComponent(process.env.PROXY_USERNAME);
const pass = encodeURIComponent(process.env.PROXY_PASSWORD);
const agent = new SocksProxyAgent(
`socks5h://${user}:${pass}@${process.env.PROXY_HOST}:${process.env.PROXY_PORT}`
);
axios
.get(process.env.TARGET_URL, {
httpAgent: agent,
httpsAgent: agent,
proxy: false,
timeout: 15000,
signal: AbortSignal.timeout(15000),
})
.then((res) => console.log(res.status, res.data))
.catch((err) => { console.error('Request failed:', err.code); process.exitCode = 1; });
This example uses socks-proxy-agent 10.1.0. Set both httpAgent and httpsAgent so http:// and https:// targets both use the SOCKS route.
The scheme controls where target DNS lookups happen: socks5h:// passes the target hostname to the SOCKS proxy, while socks5:// resolves it locally before connecting. Use socks5h:// when the target hostname should be resolved at the proxy.
Rotate proxies per request in axios
Axios does not rotate proxies by itself. You can rotate in your own code by choosing a different proxy object for each request:
const axios = require('axios');
const proxies = (process.env.PROXY_LIST || '')
.split(',')
.map((entry) => entry.trim())
.filter(Boolean)
.map((entry) => {
const parts = entry.split(':');
const [host, port] = parts;
if (parts.length !== 2 || !host || /[\s/@]/.test(host) || !/^\d+$/.test(port || '')) {
throw new Error('Each proxy must be a hostname or IPv4 address followed by :port.');
}
const numericPort = Number(port);
if (!Number.isInteger(numericPort) || numericPort < 1 || numericPort > 65535) {
throw new Error('Each proxy port must be an integer from 1 to 65535.');
}
return { protocol: 'http', host, port: numericPort };
});
if (!proxies.length) {
throw new Error('Set PROXY_LIST to one or more host:port pairs.');
}
const auth = {
username: process.env.PROXY_USERNAME,
password: process.env.PROXY_PASSWORD,
};
let next = 0;
function nextProxy() {
const proxy = proxies[next % proxies.length];
next += 1;
return { ...proxy, auth };
}
async function main() {
for (let i = 0; i < 4; i += 1) {
const proxy = nextProxy();
const res = await axios.get(process.env.TARGET_URL, { proxy, timeout: 15000, signal: AbortSignal.timeout(15000) });
console.log(`request ${i + 1} via ${proxy.host}:${proxy.port} ->`, res.status);
}
}
main().catch((err) => {
console.error('Request failed:', err.response ? err.response.status : err.code);
process.exitCode = 1;
});
Set PROXY_LIST to comma-separated host:port pairs, for example proxy-a.example:8000,proxy-b.example:8000, using your actual endpoints. This simple parser accepts hostnames or IPv4 addresses, not IPv6 literals. All endpoints in this example share the same credentials. The loop selects endpoints in order; different endpoints do not necessarily produce different exit IPs. Save the block as axios-rotate.cjs and run it after setting the variables:
export PROXY_LIST='YOUR_FIRST_PROXY_HOST:8000,YOUR_SECOND_PROXY_HOST:8000'
node axios-rotate.cjs
A provider-managed rotating gateway can remove the need for a list. With Rola IP residential proxies, copy the connection details generated for your selected region and session settings into the proxy configuration. Keep the generated username unchanged, including its routing parameters. The residential proxy setup documentation describes the configuration workflow.
If you use Node’s built-in fetch instead of Axios, the Node.js fetch proxy guide covers that setup.
Rotation changes the apparent source of a request; the target site’s terms and rate limits still apply.
Check that axios really used the proxy
To check that axios really used the proxy, call an IP echo endpoint from the same process, once with proxy: false and once with the proxy, and compare the two answers. Set IP_ECHO_URL to an endpoint you trust that returns the caller’s address:
const axios = require('axios');
const proxy = {
protocol: 'http',
host: process.env.PROXY_HOST,
port: Number(process.env.PROXY_PORT),
auth: {
username: process.env.PROXY_USERNAME,
password: process.env.PROXY_PASSWORD,
},
};
async function main() {
const url = process.env.IP_ECHO_URL;
const direct = await axios.get(url, { proxy: false, timeout: 15000, signal: AbortSignal.timeout(15000) });
const proxied = await axios.get(url, { proxy, timeout: 15000, signal: AbortSignal.timeout(15000) });
console.log('direct :', JSON.stringify(direct.data));
console.log('proxied:', JSON.stringify(proxied.data));
}
main().catch((err) => {
console.error('Request failed:', err.response ? err.response.status : err.code);
process.exitCode = 1;
});
If both responses show the same address, the IP echo check is inconclusive. Confirm that the endpoint returns the caller’s address, compare the result with the expected proxy exit, and check the proxy’s access log alongside the application’s configuration. A shared exit can produce identical results even when the routes differ.
Run the check inside the application process with its own configuration. If you use Axios 1.20.0, disable Node’s native environment-proxy setting for the direct baseline, or upgrade to 1.21.0. A proxy checker can help establish whether the proxy itself is reachable before you debug Axios, but cannot establish which route your application took.
Axios proxy not working: errors and fixes
When an axios proxy is not working, check two things first: whether the request succeeded, and whether the proxy logged it.

| Symptom | Likely cause | Fix |
|---|---|---|
Request failed: 407 |
The proxy rejected the username or password | Copy the credentials again from the provider dashboard and pass them in auth |
407 only when using HTTP_PROXY or HTTPS_PROXY |
The password contains reserved characters and axios sent the percent-encoded form | Use the proxy object with a plain auth.password |
ERR_INVALID_URL |
An unencoded @, :, or # in a proxy URL |
Percent-encode credentials in URLs, or use the proxy object |
ERR_ASSERTION with “protocol mismatch” |
protocol: 'socks5' in the native proxy option |
Use SocksProxyAgent on httpAgent and httpsAgent with proxy: false |
DEPTH_ZERO_SELF_SIGNED_CERT after a successful CONNECT |
The target’s certificate is not trusted | Add the target’s CA through https.Agent or NODE_EXTRA_CA_CERTS |
ECONNREFUSED |
Nothing is listening at the proxy host and port, or a firewall blocks it | Check the host, port, and outbound network policy |
| Request hangs | The proxy or target stalled; a request timeout may not cover every connection phase | Set both timeout and a fresh signal: AbortSignal.timeout(ms) |
ERR_NOT_SUPPORT |
httpVersion: 2 on a proxied request |
Remove httpVersion: 2 for proxied requests |
| Proxy log shows nothing, request succeeded | NO_PROXY, proxy: false, or another agent sent the request elsewhere |
Check those settings and confirm the exit with the IP echo test |
| Works locally, not on the server | The server has different proxy variables | Log whether HTTP_PROXY, HTTPS_PROXY, and NO_PROXY are set, never their values |
| Wrong exit country | Incorrect routing parameters or a geolocation-database mismatch | Check the generated settings and the provider’s expected exit before comparing geolocation sources |
403 Forbidden |
The origin or an intermediary refused access | Identify the responding server; check permissions, credentials, target rules, and request requirements |
429 Too Many Requests |
A rate limit was reached | Reduce request rate, honor Retry-After when present, and cap retries for idempotent requests |
ENOTFOUND or EAI_AGAIN |
DNS resolution failed or was temporarily unavailable | Identify whether the failed lookup concerns the proxy or target, then verify the resolver and hostname |
ETIMEDOUT or ECONNABORTED |
A connection or request timed out | Inspect the failing connection phase and proxy or origin logs before changing the proxy |
Keep retries bounded. For idempotent GET requests, allow at most two retries after the initial attempt, use increasing delays, and honor Retry-After as either a delay in seconds or an HTTP date. Set an overall time budget; if the required wait exceeds it, stop and schedule the request later. A 403 or 407 calls for an access or configuration check before retrying. Rotation does not reset a target’s access rules or rate limits.
An HTTP 429 response calls for lower request rates and handling the server’s Retry-After value; the Axios 429 troubleshooting guide explains the response and retry controls.
Never log a full proxy URL while debugging. It usually contains the username and password, and it ends up in whatever collects your logs.