TL;DR
Lỗi Error: socket hang up xảy ra khi server đóng kết nối TCP trước khi hoàn tất response. Các cách khắc phục nhanh nhất:
- Đặt
timeoutdài hơn cho request - Tắt hoặc cấu hình lại HTTP keep-alive nếu server không hỗ trợ
- Thử lại khi gặp lỗi ECONNRESET/socket hang up
- Kiểm tra xem server có đứng sau load balancer đang đóng các kết nối nhàn rỗi không
Nguyên Nhân Thực Sự
Node.js ném lỗi này khi TCP socket bị hủy giữa chừng — hoặc do server gửi TCP RST (reset), đóng kết nối sau khi timeout, hoặc mạng bị ngắt hoàn toàn. Lỗi đầy đủ thường trông như sau:
Error: socket hang up
at createHangUpError (node:_http_client:333:15)
at Socket.socketOnEnd (node:_http_client:437:23)
at Socket.emit (node:events:525:35)
at endReadableNT (node:internal/streams/readable:1358:12)
Các nguyên nhân phổ biến nhất:
- Timeout phía server: Server upstream hoặc proxy có timeout ngắn hơn thời gian xử lý request của bạn
- Mismatch keep-alive: Bạn đang tái sử dụng kết nối mà server đã đóng từ trước
- Idle timeout của load balancer: AWS ALB, nginx, hoặc HAProxy đóng các kết nối nhàn rỗi (thường sau 60 giây) trong khi agent của bạn vẫn giữ chúng mở
- Server bị crash hoặc khởi động lại: Tiến trình từ xa bị chết giữa chừng khi đang trả response
- Lỗi SSL/TLS handshake: Ít gặp hơn, nhưng các request HTTPS có thể bị hang up trong quá trình thương lượng
Cách Sửa 1: Thêm Timeout Cho Request
Khi server ngắt một request chậm, việc đặt timeout tường minh ở phía bạn sẽ cho ra lỗi rõ ràng hơn và có cơ hội thử lại. Nếu không, Node sẽ chỉ ngồi chờ mãi.
Dùng module https gốc
const https = require('https');
const req = https.request({
hostname: 'api.example.com',
path: '/data',
method: 'GET',
timeout: 10000, // 10 giây
}, (res) => {
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => console.log(body));
});
req.on('timeout', () => {
req.destroy(); // kích hoạt sự kiện 'error'
});
req.on('error', (err) => {
console.error('Request failed:', err.message);
});
req.end();
Dùng axios
const axios = require('axios');
const response = await axios.get('https://api.example.com/data', {
timeout: 10000, // 10s — áp dụng cho cả connection + response
});
Cách Sửa 2: Tắt Keep-Alive hoặc Tinh Chỉnh Agent
Đây là nguyên nhân khó phát hiện nhất. HTTP agent của bạn bật keep-alive, server lặng lẽ đóng kết nối từ phía của nó, và Node cố tái sử dụng socket chết đó cho request tiếp theo. Kết quả: hang up.
Tắt hoàn toàn keep-alive (sửa nhanh)
const https = require('https');
const agent = new https.Agent({ keepAlive: false });
const req = https.request({
hostname: 'api.example.com',
path: '/data',
agent,
}, (res) => { /* ... */ });
Tinh chỉnh keep-alive để khớp với timeout của server (sửa tốt hơn)
AWS ALB có idle timeout mặc định là 60 giây. Đặt keepAliveMsecs của agent thấp hơn con số đó — 30 giây là mức an toàn:
const https = require('https');
const agent = new https.Agent({
keepAlive: true,
keepAliveMsecs: 30000, // gửi TCP keep-alive mỗi 30 giây
maxSockets: 50,
maxFreeSockets: 10,
timeout: 60000,
});
// Truyền agent này vào axios globally
const axios = require('axios');
const client = axios.create({
httpsAgent: agent,
timeout: 15000,
});
Cách Sửa 3: Thử Lại Khi Gặp Lỗi Socket
Sự cố mạng thoáng qua vẫn xảy ra. Một wrapper thử lại đơn giản sẽ bắt các lỗi hang up trước khi chúng nổi lên và làm crash ứng dụng của bạn:
async function fetchWithRetry(url, options = {}, retries = 3) {
for (let attempt = 1; attempt err.message.includes(code) || err.code === code);
if (isSocketError && attempt setTimeout(r, delay));
continue;
}
throw err;
}
}
}
Cách Sửa 4: Kiểm Tra Cài Đặt Proxy hoặc Load Balancer
Các ứng dụng chạy sau nginx hoặc AWS ALB có thêm một lớp có thể âm thầm hủy kết nối. Đây là những điều cần kiểm tra:
nginx upstream keepalive
upstream backend {
server 127.0.0.1:3000;
keepalive 32; # giữ tối đa 32 kết nối nhàn rỗi
}
server {
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection ""; # bắt buộc cho keepalive upstream
proxy_read_timeout 90s; # khớp với thời gian response của app
}
}
AWS ALB idle timeout
Idle timeout mặc định của ALB là 60 giây. Các request mất nhiều hơn thời gian đó sẽ bị cắt. Tăng giá trị này trong AWS Console tại Load Balancers → Attributes → Idle timeout. Ngoài ra, có thể giữ keepalive interval của HTTP agent dưới 60 giây như đã trình bày ở Cách Sửa 2.
Cách Sửa 5: Vấn Đề Chứng Chỉ HTTPS
Lỗi chứng chỉ trên các dịch vụ nội bộ hoặc môi trường staging có thể khiến server ngắt kết nối ngay lập tức — và điều đó hiển thị dưới dạng socket hang up, không phải lỗi cert. Khá khó phát hiện.
// Chỉ để debug tạm thời — KHÔNG BAO GIỜ dùng trong production
const agent = new https.Agent({ rejectUnauthorized: false });
// Tốt hơn: trỏ đến file CA cert nội bộ của bạn
const fs = require('fs');
const agent = new https.Agent({
ca: fs.readFileSync('/path/to/internal-ca.crt'),
});
Tắt rejectUnauthorized mà lỗi biến mất? Bạn đang gặp vấn đề về tin cậy chứng chỉ, không phải vấn đề mạng.
Xác Nhận Đã Sửa Được
Đã đến lúc xác nhận cách sửa thực sự có hiệu quả. Chạy các kiểm tra sau:
# Kiểm tra endpoint trực tiếp bằng curl — nếu curl hoạt động nhưng Node không, vấn đề là cấu hình agent
curl -v --max-time 15 https://api.example.com/data
# Kiểm tra xem keep-alive có thực sự được dùng không
curl -v --keepalive-time 30 https://api.example.com/data 2>&1 | grep -i 'keep-alive\|connection'
# Trong Node, log việc tái sử dụng socket
agent.on('free', (socket, options) => {
console.log('Socket returned to pool:', options.hostname);
});
Chạy request của bạn 5–10 lần trong vòng lặp. Lỗi keep-alive cũ thường xuất hiện vào request đầu tiên sau khi nhàn rỗi 60 giây. Hãy mô phỏng điều đó bằng cách chờ một phút rồi thử lại — nếu chỉ thất bại sau khi chờ, Cách Sửa 2 chính là giải pháp của bạn.
Mẹo Thêm
Việc debug kết nối mạng giữa Node.js và các API upstream — đặc biệt trong môi trường container hóa — thường xoay quanh cấu hình routing và subnet. Subnet Calculator trên ToolCraft rất hữu ích để kiểm tra các dải CIDR và xác nhận rằng nguồn và đích thực sự có thể định tuyến đến nhau. Tiết kiệm rất nhiều thời gian trước khi bạn bắt đầu đụng vào các quy tắc firewall.
Thêm một pattern đáng chú ý: nếu lỗi chỉ xuất hiện lẻ tẻ dưới tải production, hãy thêm structured logging xung quanh các HTTP call của bạn. Log err.code, hostname đích và thời gian request. Các lỗi tập trung trong khoảng 58–62 giây? Đó gần như chắc chắn là idle timeout của load balancer.

