WeProxy

Hướng dẫn

Cách dùng proxy trong Node.js: ví dụ với axios, undici và fetch

đọc 12 phút

Trong Node.js, proxy được khai báo bằng NODE_USE_ENV_PROXY với fetch tích hợp sẵn, bằng ProxyAgent trong undici và bằng thiết lập proxy trong axios. Ví dụ HTTP và SOCKS5, quản lý sticky session, xác minh IP đầu ra và các lỗi proxy theo từng client.

Tóm tắt nhanh

  • Node.js không tự đọc thiết lập proxy; fetch tích hợp sẵn chỉ dùng các biến HTTP_PROXY, HTTPS_PROXY và NO_PROXY khi được khởi động với NODE_USE_ENV_PROXY=1 hoặc --use-env-proxy (Node.js 24 và các bản sau 22.21).
  • Trong undici, proxy được truyền vào request dưới dạng dispatcher: new ProxyAgent("http://USER:PASSWORD@host:port"); hàm fetch cũng phải được import từ cùng gói undici, nếu không khác biệt phiên bản có thể khiến request thất bại.
  • axios nhận proxy qua thiết lập proxy: { protocol, host, port, auth }; mật khẩu trong đối tượng auth được viết nguyên dạng, không cần mã hóa. Khi dùng https-proxy-agent, hãy thêm proxy: false để tránh xung đột với biến môi trường.
  • Với SOCKS5, axios và node:https dùng socks-proxy-agent, còn undici dùng Socks5ProxyAgent đang ở trạng thái thử nghiệm; scheme socks5h giao việc phân giải DNS cho proxy.
  • undici giữ nguyên nhân thực sự của lỗi trong err.cause; khi nhận 407, thay vì thử lại, hãy kiểm tra mật khẩu đã được mã hóa bằng encodeURIComponent chưa và gói còn hoạt động không.

Trong Node.js có ba cách dùng proxy phổ biến: với fetch tích hợp sẵn, bạn khởi động Node bằng NODE_USE_ENV_PROXY=1 và khai báo biến HTTPS_PROXY; với gói undici, bạn truyền một instance ProxyAgent vào request qua tùy chọn dispatcher; còn với axios, bạn dùng thiết lập proxy. Trong mọi trường hợp, địa chỉ proxy có dạng http://USER:PASSWORD@host:port; với SOCKS5, bạn cần socks-proxy-agent hoặc lớp Socks5ProxyAgent của undici.

Chọn cách nào phụ thuộc vào HTTP client bạn đang dùng. Node.js không tự đọc thiết lập proxy cho http.request và fetch; bạn hoặc bật hỗ trợ biến môi trường, hoặc khai báo proxy riêng cho từng client. Các ví dụ bên dưới dùng địa chỉ gateway của WeProxy là gw.weproxy.com.tr:8989. USER và PASSWORD là thông tin đăng nhập trong bảng điều khiển của bạn.

Viết địa chỉ proxy trong Node.js như thế nào?

Mọi client đều nhận proxy theo cùng một dạng URL:

scheme://username:password@host:port

Scheme là giao thức của proxy server. Dù site đích dùng HTTPS, địa chỉ HTTP proxy vẫn bắt đầu bằng http://; client gửi request CONNECT tới proxy và kết nối TLS được thiết lập bên trong tunnel đó. Nếu bạn viết địa chỉ với https://, client sẽ cố nói chuyện TLS với chính proxy, và undici báo lỗi ERR_SSL_WRONG_VERSION_NUMBER. Khác biệt giữa HTTP proxy và SOCKS5 được giải thích trong bài HTTP vs SOCKS5.

Nếu mật khẩu chứa @, :, / hoặc #, hãy mã hóa bằng encodeURIComponent trước khi đưa vào URL. Một dấu @ chưa mã hóa làm hỏng việc phân tích địa chỉ và undici báo TypeError: Invalid URL:

const user = encodeURIComponent(process.env.WEPROXY_USERNAME);
const pass = encodeURIComponent(process.env.WEPROXY_PASSWORD);
export const PROXY = `http://${user}:${pass}@gw.weproxy.com.tr:8989`;

Hãy giữ thông tin đăng nhập trong biến môi trường hoặc trình quản lý secret, để khi code được đẩy lên Git thì mật khẩu không đi theo. Nếu bảng điều khiển cho bạn danh sách dạng ip:port:user:pass, công cụ chuyển đổi định dạng proxy sẽ chuyển các dòng này sang dạng URL. Địa chỉ IPv6 và các chi tiết như socks5h có trong hướng dẫn định dạng proxy.

Dùng proxy với fetch tích hợp sẵn của Node.js như thế nào?

Từ Node.js 24 và các bản sau 22.21, fetch tích hợp sẵn có thể đọc các biến HTTP_PROXY, HTTPS_PROXY và NO_PROXY. Tính năng này mặc định tắt và được bật bằng biến NODE_USE_ENV_PROXY=1 hoặc cờ --use-env-proxy. Không có cờ này, Node bỏ qua các biến trên và request đi thẳng ra từ IP của bạn.

export HTTPS_PROXY="http://USER:[email protected]:8989"
export HTTP_PROXY="$HTTPS_PROXY"
export NO_PROXY="localhost,127.0.0.1"
NODE_USE_ENV_PROXY=1 node app.mjs

Trong PowerShell, thiết lập tương tự như sau:

$env:HTTPS_PROXY = "http://USER:[email protected]:8989"
$env:HTTP_PROXY = $env:HTTPS_PROXY
node --use-env-proxy app.mjs

File app.mjs không cần dòng nào liên quan đến proxy:

const res = await fetch('https://api.ipify.org?format=json', {
  signal: AbortSignal.timeout(30_000),
});
console.log(await res.json()); // { ip: '<IP đầu ra của proxy>' }

Ở các phiên bản hiện tại, cờ này còn chuyển global agent của module node:http và node:https qua proxy; các lời gọi kiểu cũ như https.get đi qua proxy mà không phải sửa code. Chi tiết có trong tài liệu CLI của Node.js. Cách này hợp khi bạn muốn đưa một ứng dụng có sẵn qua proxy mà không động vào code. Toàn bộ tiến trình dùng chung một proxy; nếu mỗi request cần một proxy khác, hãy chuyển sang cách tiếp theo.

Dùng proxy với undici ProxyAgent như thế nào?

undici là HTTP client nằm bên dưới phần triển khai fetch của Node.js và cũng được phát hành thành gói riêng trên npm. Lớp ProxyAgent cho phép khai báo proxy ở cấp request:

npm install undici
import { ProxyAgent, fetch } from 'undici';
import { PROXY } from './proxy.mjs';

const dispatcher = new ProxyAgent(PROXY);

const res = await fetch('https://api.ipify.org?format=json', {
  dispatcher,
  signal: AbortSignal.timeout(30_000),
});
console.log(await res.json());

await dispatcher.close();

Hãy import hàm fetch từ cùng gói với ProxyAgent. Phiên bản undici đi kèm Node có thể cũ hơn bản trên npm. Khi bạn truyền đối tượng ProxyAgent của undici 8 từ npm vào fetch tích hợp sẵn của Node.js 24, request thất bại với fetch failed, nguyên nhân bên dưới là invalid onRequestStart method.

Nếu không muốn nhúng mật khẩu vào URL, bạn có thể truyền thông tin đăng nhập dưới dạng giá trị header. Cách này không cần percent-encode:

const basic = Buffer.from(
  `${process.env.WEPROXY_USERNAME}:${process.env.WEPROXY_PASSWORD}`
).toString('base64');

const dispatcher = new ProxyAgent({
  uri: 'http://gw.weproxy.com.tr:8989',
  token: `Basic ${basic}`,
});

Để mọi lời gọi fetch trong ứng dụng đi qua proxy, hãy dùng setGlobalDispatcher(new ProxyAgent(PROXY)). Ngoài ra còn một lựa chọn đọc biến môi trường mà không cần cờ tiến trình: EnvHttpProxyAgent tự đọc giá trị HTTPS_PROXY và NO_PROXY. Danh sách đầy đủ các tùy chọn có trong tài liệu undici ProxyAgent.

Dùng proxy với axios như thế nào?

Trong Node.js, axios nhận proxy qua thiết lập proxy. Vì thông tin đăng nhập nằm ở các trường riêng, bạn không phải mã hóa ký tự đặc biệt:

import axios from 'axios';

const client = axios.create({
  proxy: {
    protocol: 'http',
    host: 'gw.weproxy.com.tr',
    port: 8989,
    auth: {
      username: process.env.WEPROXY_USERNAME,
      password: process.env.WEPROXY_PASSWORD,
    },
  },
  timeout: 30_000,
});

const { data } = await client.get('https://api.ipify.org?format=json');
console.log(data);

Các phiên bản axios hiện tại mở tunnel CONNECT tới proxy cho đích HTTPS. Bản 1.16.1 đã sửa một lỗi khiến, trong một số cấu hình, nội dung request HTTPS có thể được gửi tới HTTP proxy mà không mã hóa (changelog của axios). Nếu đang dùng phiên bản cũ, hãy cập nhật axios hoặc giao tunnel cho gói https-proxy-agent:

import axios from 'axios';
import { HttpsProxyAgent } from 'https-proxy-agent';
import { PROXY } from './proxy.mjs';

const client = axios.create({
  httpsAgent: new HttpsProxyAgent(PROXY),
  proxy: false,
});

Theo mặc định, axios đọc các biến HTTPS_PROXY và NO_PROXY; thiếu dòng proxy: false, proxy trong môi trường có thể xung đột với agent của bạn. Còn nếu giá trị HTTPS_PROXY chứa mật khẩu đã percent-encode, axios gửi nguyên dạng đã mã hóa tới proxy và kết quả là 407. Khi mật khẩu có ký tự đặc biệt, hãy dùng đối tượng auth của axios.

Dùng SOCKS5 proxy trong Node.js như thế nào?

Cả fetch tích hợp sẵn lẫn axios đều không tự nói được SOCKS5. Với axios và node:https, bạn dùng gói socks-proxy-agent:

npm install socks-proxy-agent
import axios from 'axios';
import { SocksProxyAgent } from 'socks-proxy-agent';

const user = encodeURIComponent(process.env.WEPROXY_USERNAME);
const pass = encodeURIComponent(process.env.WEPROXY_PASSWORD);
const agent = new SocksProxyAgent(`socks5h://${user}:${pass}@gw.weproxy.com.tr:8989`);

const client = axios.create({ httpAgent: agent, httpsAgent: agent, proxy: false });
console.log((await client.get('https://api.ipify.org')).data);

Scheme socks5h để proxy phân giải tên miền; nếu viết socks5, tên miền đích được phân giải trên DNS server của chính bạn. Nếu dùng undici, lớp Socks5ProxyAgent làm cùng việc đó và luôn để proxy phân giải tên miền. Lớp này hiện vẫn được đánh dấu thử nghiệm và in ra ExperimentalWarning ở lần dùng đầu tiên:

import { Socks5ProxyAgent, fetch } from 'undici';

const dispatcher = new Socks5ProxyAgent(`socks5://${user}:${pass}@gw.weproxy.com.tr:8989`);
const res = await fetch('https://api.ipify.org', { dispatcher });
console.log(await res.text());

Hỗ trợ SOCKS5 khác nhau theo gói. Rotating Residential IPv4 cung cấp cả HTTP và SOCKS5; Mobile 5G Proxy dùng SIM thật chỉ chạy với HTTP. Lưu lượng UDP như game hay VoIP không đi qua các client này; các cấu hình cần UDP được trình bày trong hướng dẫn UDP proxy.

So sánh axios, undici và fetch

Tính năngfetch tích hợp sẵnundiciaxios
Cài đặtKhông cần (Node.js 18+)npm install undicinpm install axios
Cách khai báo proxyNODE_USE_ENV_PROXY=1 + HTTPS_PROXYdispatcher: new ProxyAgent(...)proxy: {...} hoặc httpsAgent
Proxy khác nhau cho từng requestKhông, dùng chung cho cả tiến trìnhCóCó
Thông tin đăng nhậpTrong URLTrong URL hoặc tokenĐối tượng auth
SOCKS5KhôngSocks5ProxyAgent (thử nghiệm)socks-proxy-agent
Biến môi trườngChỉ khi có cờQua EnvHttpProxyAgentMặc định bật
Phù hợp choĐưa qua proxy mà không sửa codeCode mới, nhiều proxy, mức đồng thời caoDự án axios có sẵn

Với dự án mới, chỉ một gói undici đã cho bạn proxy theo từng request, SOCKS5 và thiết lập connection pool. Nếu codebase đã xây trên axios thì không cần đổi client; thiết lập proxy hoặc https-proxy-agent là đủ.

Quản lý IP rotating và sticky trong Node.js như thế nào?

Với gateway rotating, địa chỉ proxy giữ nguyên còn IP đầu ra thay đổi theo pool. Ở phía Node.js, điều cần chú ý là việc tái sử dụng kết nối. undici ProxyAgent dùng lại tunnel đã mở cho mỗi đích ở các request sau; từ Node.js 19, keepAlive mặc định bật trên global agent của http và https. Nếu IP chỉ đổi khi có kết nối mới, các request liên tiếp từ cùng một agent có thể đi ra từ cùng một IP. Để biết gateway xoay IP theo từng request hay theo từng kết nối, hãy truy vấn api.ipify.org hai lần bằng cùng một agent, sau đó hai lần bằng hai agent riêng.

Với các tác vụ như đăng nhập, giỏ hàng hay form nhiều bước, bạn cần giữ cùng một IP xuyên suốt các request. Trong pool residential và mobile của WeProxy, bạn thêm một session ID vào username để làm việc này. Định dạng username và thời lượng session khác nhau theo sản phẩm, vì vậy hãy lấy cả hai từ bảng điều khiển. Trong code, chỉ cần mở một agent riêng cho mỗi tài khoản:

import { ProxyAgent, fetch } from 'undici';

// Username được copy từ bảng điều khiển; mỗi profile mang session ID riêng.
const PROFILES = {
  'account-1': 'USER_WITH_SESSION_1',
  'account-2': 'USER_WITH_SESSION_2',
};
const pass = encodeURIComponent(process.env.WEPROXY_PASSWORD);

const agents = Object.fromEntries(
  Object.entries(PROFILES).map(([name, user]) => [
    name,
    new ProxyAgent(`http://${encodeURIComponent(user)}:${pass}@gw.weproxy.com.tr:8989`),
  ])
);

for (const [name, dispatcher] of Object.entries(agents)) {
  const res = await fetch('https://api.ipify.org', { dispatcher });
  console.log(name, await res.text());
}

Hai profile không được dùng chung session ID; nếu dùng chung, website đích sẽ thấy hai người dùng khác nhau đến từ cùng một IP. Tác vụ nào cần sticky, tác vụ nào cần rotating được trình bày kèm bảng tình huống trong hướng dẫn chọn sticky hay rotating. Gói cung cấp cả hai kiểu session có trên trang rotating residential proxy.

Khi cần quét nhiều URL, đừng khởi chạy tất cả cùng lúc bằng Promise.all mà hãy giới hạn mức đồng thời. Một pool nhỏ không cần gói bổ sung:

async function runPool(items, limit, worker) {
  const results = [];
  let next = 0;
  const lanes = Array.from({ length: limit }, async () => {
    while (next < items.length) {
      const i = next++;
      results[i] = await worker(items[i]).catch((err) => err);
    }
  });
  await Promise.all(lanes);
  return results;
}

const urls = Array(20).fill('https://api.ipify.org');
const ips = await runPool(urls, 5, async (url) =>
  (await fetch(url, { dispatcher: agents['account-1'] })).text()
);

Giới hạn ở đây do website đích quyết định: một loạt request dồn dập từ cùng một IP sẽ chạm rate limit của đích và nhận về 429.

Làm sao xác minh proxy đang hoạt động?

Cách kiểm tra ngắn nhất là truy vấn IP đầu ra khi không dùng và khi dùng proxy, rồi so sánh:

import { ProxyAgent, fetch as undiciFetch } from 'undici';
import { PROXY } from './proxy.mjs';

const dispatcher = new ProxyAgent(PROXY);
const direct = await (await fetch('https://api.ipify.org')).text();
const viaProxy = await (await undiciFetch('https://api.ipify.org', { dispatcher })).text();

console.log({ direct, viaProxy });
if (direct === viaProxy) throw new Error('Request không đi qua proxy');

Nếu hai giá trị giống nhau thì thiết lập proxy chưa tới được request. Với fetch tích hợp sẵn, hãy kiểm tra cờ NODE_USE_ENV_PROXY; với axios, kiểm tra xung đột biến môi trường qua proxy: false; và trong mọi trường hợp, xem lại danh sách NO_PROXY. Để xem quốc gia và mạng của IP proxy, hãy dùng công cụ My IP. Để test proxy độc lập với code, hãy dùng proxy checker; một proxy chạy được ở đó nhưng không chạy trong Node cho thấy vấn đề nằm ở thiết lập client. Nếu muốn làm cùng phép thử từ terminal, các lệnh trong bài dùng proxy với cURL sẽ giúp bạn.

Lỗi proxy trong Node.js và cách xử lý

Cùng một sự cố, nhưng mỗi client lại báo bằng một lỗi khác. Các kết quả trong bảng được ghi lại trên một proxy test cục bộ với Node.js 24, undici 8, axios 1.21 và https-proxy-agent 9:

Tình huốngundici fetchaxiosnode:https + HttpsProxyAgent
Proxy từ chối thông tin đăng nhập (407)fetch failed; nguyên nhân bên dưới là “Proxy response (407) !== 200 when HTTP Tunneling”AxiosError, status: 407Không có lỗi, statusCode của phản hồi là 407
Không kết nối được tới proxyfetch failed; nguyên nhân ECONNREFUSEDECONNREFUSEDSự kiện error, ECONNREFUSED
Hết thời gian chờTimeoutError qua AbortSignal.timeoutECONNABORTEDBắt bằng req.setTimeout
Địa chỉ proxy viết với https://ERR_SSL_WRONG_VERSION_NUMBEREPROTO (“wrong version number”)EPROTO (“wrong version number”)
Mật khẩu chưa mã hóaTypeError: Invalid URLKhông gặp vấn đề khi dùng đối tượng authTypeError: Invalid URL

undici giữ nguyên nhân thực sự của lỗi trong err.cause; nếu chỉ in err.message, mọi lỗi đều hiện là “fetch failed”. Hãy ghi thêm err.cause?.code và err.cause?.message vào log.

Khi gặp 407, trước tiên hãy kiểm tra mật khẩu đã được mã hóa chưa, sau đó kiểm tra gói còn hoạt động không. Nếu bạn dùng whitelist IP, IP đầu ra của server chạy code phải được đăng ký trong bảng điều khiển; script chạy tốt trên máy cục bộ nhưng bắt đầu nhận 407 khi chuyển lên server thì nguyên nhân là đây. Cách xác thực áp dụng cho từng sản phẩm có trong hướng dẫn xác thực proxy. Đừng thử lại với 407: cùng thông tin đăng nhập thì kết quả vẫn vậy.

429, 502, 503 và 504 có thể chỉ là tạm thời và cần thử lại với khoảng chờ tăng dần. Mỗi mã đến từ tầng nào được giải thích trong hướng dẫn mã lỗi proxy. Nếu kết nối liên tục bị timeout, có thể mạng doanh nghiệp hoặc nhà cung cấp cloud của bạn đang chặn kết nối ra port 8989. Khi gặp lỗi chứng chỉ, đừng tắt xác minh bằng NODE_TLS_REJECT_UNAUTHORIZED=0; thiết lập này vô hiệu hóa việc xác minh TLS cho toàn bộ tiến trình.

Tuân thủ và bảo mật khi dùng proxy với Node.js

Nếu bạn viết script thu thập dữ liệu, dù request đi qua proxy, bạn vẫn cần tuân thủ file robots.txt (RFC 9309) và điều khoản sử dụng của website đích, đồng thời giữ tốc độ request ở mức site chịu được. Nếu bạn xử lý dữ liệu cá nhân, các nghĩa vụ theo KVKK và GDPR vẫn áp dụng, bất kể có dùng proxy hay không. Những cách sử dụng được phép và không được phép với dịch vụ WeProxy được ghi trên trang chính sách sử dụng chấp nhận được.

Vì URL proxy chứa mật khẩu, nó rất dễ lộ ra trong thông báo lỗi và log. Trước khi ghi log, hãy phân tích địa chỉ bằng new URL(PROXY) và xóa trường password. Các bước cài đặt có trên trang tích hợp Node.js, còn dự án mẫu chạy được nằm trong repository weproxy-io/nodejs-proxy. Với tự động hóa trình duyệt, hãy xem tích hợp Puppeteer; để làm cùng các việc này bằng Python, xem bài dùng proxy trong Python. Với tác vụ scraping, cách chọn loại IP và kiểu session có trên trang web scraping.

Câu hỏi thường gặp

Dùng proxy với fetch trong Node.js như thế nào?

Từ Node.js 24 và các bản sau 22.21, hãy gán giá trị http://USER:PASSWORD@host:port cho biến HTTPS_PROXY rồi khởi động ứng dụng bằng NODE_USE_ENV_PROXY=1 hoặc node --use-env-proxy; các request của fetch tích hợp sẵn sẽ đi qua proxy. Nếu mỗi request cần một proxy khác, hãy cài gói undici từ npm, truyền đối tượng ProxyAgent qua tùy chọn dispatcher và import cả hàm fetch từ undici.

Tại sao đã khai báo HTTPS_PROXY mà Node.js vẫn dùng IP thật?

Theo mặc định, Node.js không đọc proxy trong biến môi trường. fetch tích hợp sẵn và node:https cần NODE_USE_ENV_PROXY=1 hoặc --use-env-proxy; các phiên bản cũ không có hỗ trợ này. Cũng có thể danh sách NO_PROXY bao gồm tên miền đích. Truy vấn api.ipify.org khi có và không có proxy rồi so sánh kết quả sẽ giúp bạn tìm ra vấn đề nhanh chóng.

Khai báo proxy có username và mật khẩu trong axios như thế nào?

Trong axios.create, hãy truyền cho thiết lập proxy các trường protocol: "http", host, port và auth: { username, password }. Mật khẩu trong đối tượng auth được viết nguyên dạng, không cần percent-encode. Với các phiên bản axios cũ, cách an toàn hơn cho request HTTPS là truyền gói https-proxy-agent làm httpsAgent và thêm proxy: false; bạn cũng nên giữ axios ở phiên bản mới.

Dùng SOCKS5 proxy trong Node.js như thế nào?

fetch tích hợp sẵn và axios không tự hỗ trợ SOCKS5. Với axios hoặc node:https, hãy cài gói socks-proxy-agent, tạo một agent với địa chỉ socks5h://USER:PASSWORD@host:port rồi gán cho cả trường httpAgent lẫn httpsAgent. Nếu dùng undici, lớp Socks5ProxyAgent đang thử nghiệm làm cùng việc đó. Hỗ trợ SOCKS5 khác nhau theo gói; Mobile 5G Proxy chỉ chạy với HTTP.

Tại sao undici chỉ báo lỗi “fetch failed”?

undici fetch ném lỗi mạng dưới dạng một TypeError chung và giữ nguyên nhân thực sự trong trường err.cause. Ví dụ, với 407, cuối chuỗi lỗi là thông báo “Proxy response (407) !== 200 when HTTP Tunneling”, còn khi không kết nối được tới proxy thì có mã ECONNREFUSED. Hãy in thêm err.cause?.code và err.cause?.message vào log, phân loại lỗi dựa trên đó và chỉ thử lại với lỗi tạm thời.

Bắt Đầu

Kiểm tra thiết lập proxy của bạn ngay hôm nay

Kiểm tra khả năng kết nối bằng các công cụ miễn phí hoặc trao đổi với bộ phận bán hàng để chọn gói phù hợp.