Web

Using Axios with a Custom HTTPS Agent in React and Node.js

Web · Published 2026-10-27

Axios accepts an httpsAgent option, an instance of Node's https.Agent, which controls TLS and connection reuse: trusting a private certificate authority, presenting a client certificate, keeping sockets alive. It only works where Axios runs on Node.js. In a React app running in the browser the option is ignored, because the browser owns TLS. This article shows both sides and how to structure a React project so the agent lives where it can have an effect.

The Node examples were run with Axios 1.21 on Node.js 22 against a local HTTPS server with a self-signed certificate.

Why the agent does nothing in the browser

Axios picks an adapter at runtime: on Node.js it uses the http and https modules, in the browser it uses XMLHttpRequest or fetch. Browser APIs expose no way to choose trusted certificates, client keys or socket pools; those are decided by the browser and the operating system. So in client-side React code:

The agent matters wherever React code runs on Node: server-side rendering, framework server functions and API routes, build scripts, and tests run with Node-based runners.

A custom agent on Node.js

The typical case is an internal API whose certificate is signed by a company CA that Node does not trust by default. Without help, the request fails:

import axios from "axios";

try {
  await axios.get("https://localhost:8443/health");
} catch (err) {
  console.log("default:", err.code);
}
default: DEPTH_ZERO_SELF_SIGNED_CERT

Create one agent that trusts the CA, and attach it to an Axios instance:

import https from "node:https";
import fs from "node:fs";
import axios from "axios";

const httpsAgent = new https.Agent({
  ca: fs.readFileSync("ca.pem"),   // the private CA (or self-signed cert)
  keepAlive: true,                 // reuse TLS connections
  maxSockets: 10,                  // cap parallel sockets per host
});

const api = axios.create({
  baseURL: "https://localhost:8443",
  httpsAgent,
  timeout: 5000,
});

const { status, data } = await api.get("/health");
console.log("custom agent:", status, data);
custom agent: 200 { ok: true, path: '/health' }

Passing ca replaces Node's default list of trusted roots for this agent, so this instance will no longer reach public HTTPS sites. Use a separate Axios instance for public APIs, or set the NODE_EXTRA_CA_CERTS environment variable to a PEM file, which adds certificates to the defaults for the whole process.

Other useful agent options

OptionUse
cert, key (or pfx + passphrase)Present a client certificate for mutual TLS
keepAlive, keepAliveMsecsReuse connections instead of a TLS handshake per request
maxSockets, maxFreeSocketsLimit concurrency and idle sockets per host
servernameOverride the SNI name when connecting by IP address
minVersionRequire, for example, "TLSv1.3"

Create the agent once per process and share it. A new agent per request discards the connection pool and gives you a full handshake every time. Call httpsAgent.destroy() on shutdown so open keep-alive sockets do not keep the process running. The same "one pool, reused" rule applies in Python: see sending HTTP requests with urllib3.

Never ship rejectUnauthorized: false

The most copied snippet for this problem is:

// Do not use outside a throwaway local experiment
const insecure = new https.Agent({ rejectUnauthorized: false });

It turns off certificate verification, which means anyone on the network path can impersonate the server and read or modify the traffic, including tokens you send. Setting NODE_TLS_REJECT_UNAUTHORIZED=0 does the same for every request in the process, and Node prints a warning for good reason. Trusting the specific CA with ca solves the same error without that risk.

Where to put the agent in a React project

The clean structure is a backend-for-frontend: the browser calls your own server, and your server calls the internal API with the agent.

  1. Server module. Create the agent and Axios instance in a file that is only imported by server code (an API route, a server action, an Express handler). Many frameworks let you mark such modules as server-only so the bundler refuses to include them in client code.
  2. Browser code. Calls a same-origin path such as /api/orders with plain Axios or fetch, no agent.
  3. Secrets. Client keys, CA bundles and API tokens stay on the server, read from files or environment variables, never in the JavaScript bundle.
// server/internalApi.js (server-only)
import https from "node:https";
import fs from "node:fs";
import axios from "axios";

export const internalApi = axios.create({
  baseURL: process.env.INTERNAL_API_URL,
  timeout: 5000,
  httpsAgent: new https.Agent({
    ca: fs.readFileSync(process.env.INTERNAL_CA_PATH),
    keepAlive: true,
  }),
});

// an API route or server handler
export async function getOrders() {
  const { data } = await internalApi.get("/orders");
  return data;
}
// client component: no agent, same-origin call
import axios from "axios";
import { useEffect, useState } from "react";

export function Orders() {
  const [orders, setOrders] = useState([]);
  useEffect(() => {
    const controller = new AbortController();
    axios.get("/api/orders", { signal: controller.signal })
      .then((res) => setOrders(res.data))
      .catch((err) => { if (!axios.isCancel(err)) console.error(err); });
    return () => controller.abort();
  }, []);
  return <ul>{orders.map((o) => <li key={o.id}>{o.label}</li>)}</ul>;
}

The AbortController cancels the request if the component unmounts, which avoids state updates on unmounted components and duplicate requests in development mode. This split is the same separation of concerns described in what a CRUD application is: the browser talks to your API, your API talks to everything else.

Proxies and the agent

Axios on Node reads HTTPS_PROXY and NO_PROXY from the environment. When a proxy is in use, the TLS connection to the target goes through a tunnel, and the interaction between the proxy configuration and a custom agent has changed across Axios releases. If requests behave differently behind a corporate proxy, set proxy: false on the instance and use a dedicated proxy agent package, or add the internal host to NO_PROXY.

Troubleshooting

Summary

An https.Agent passed as httpsAgent controls trusted CAs, client certificates and keep-alive for Axios on Node.js, and is ignored in the browser. Put it in server-only code, create it once, trust specific CAs instead of disabling verification, and let the React client call your own same-origin API.

Sources

Read next

More in Web