The HPE_UNEXPECTED_EOF error occurs when Node.js HTTP parser encounters an unexpected end-of-file during HTTP response parsing. This typically happens when the server closes the connection prematurely before sending the complete HTTP response, leaving the client with incomplete data.
The HPE_UNEXPECTED_EOF (HTTP Parser Error - Unexpected End Of File) is thrown by Node.js's internal HTTP parser (llhttp) when it encounters an unexpected termination of the HTTP response stream. This error specifically indicates that the connection was closed by the server or network before the HTTP parser could finish reading the complete response based on the Content-Length header or chunked transfer encoding expectations. When an HTTP response is being received, the parser expects certain data patterns—either a specific number of bytes indicated by the Content-Length header, or properly terminated chunked encoding with a final zero-length chunk. If the socket closes before this expected data is fully received, the parser raises HPE_UNEXPECTED_EOF to signal that the response is incomplete. This is distinct from other HTTP parser errors like HPE_INVALID_CONSTANT or HPE_HEADER_OVERFLOW, as it specifically relates to premature connection termination rather than malformed HTTP data or protocol violations.
Implement proper error handling with exponential backoff to gracefully handle transient connection issues:
const http = require('http');
function makeRequestWithRetry(options, maxRetries = 3) {
return new Promise((resolve, reject) => {
let attempt = 0;
function attemptRequest() {
attempt++;
const req = http.request(options, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
resolve(data);
});
res.on('error', handleError);
});
req.on('error', handleError);
function handleError(err) {
if (err.code === 'HPE_UNEXPECTED_EOF' && attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 100; // Exponential backoff
console.log(`Retry attempt ${attempt} after ${delay}ms`);
setTimeout(attemptRequest, delay);
} else {
reject(err);
}
}
req.end();
}
attemptRequest();
});
}This adds resilience against temporary connection issues.
Set explicit timeout values to prevent indefinite hangs and ensure connections are properly managed:
const http = require('http');
const options = {
hostname: 'example.com',
path: '/api/data',
method: 'GET',
timeout: 30000, // 30 second timeout
// Ensure server keep-alive is longer than client timeout
headers: {
'Connection': 'keep-alive',
'Keep-Alive': 'timeout=5, max=100'
}
};
const req = http.request(options, (res) => {
// Handle response
});
// Handle timeout specifically
req.on('timeout', () => {
console.error('Request timed out');
req.destroy(); // Destroy the request
});
req.on('error', (err) => {
if (err.code === 'HPE_UNEXPECTED_EOF') {
console.error('Server closed connection prematurely');
}
});
req.end();For servers, ensure your keep-alive timeout is longer than client expectations:
const server = http.createServer((req, res) => {
// Handle requests
});
// Set server keep-alive timeout to 65 seconds
// (longer than typical client 60s timeout)
server.keepAliveTimeout = 65000;
server.headersTimeout = 66000; // Slightly longer than keepAliveTimeout
server.listen(3000);Consider using more robust HTTP client libraries that handle connection issues more gracefully:
// Using axios with retry logic
const axios = require('axios');
const axiosRetry = require('axios-retry');
const client = axios.create({
timeout: 30000,
httpAgent: new http.Agent({
keepAlive: true,
keepAliveMsecs: 30000
})
});
// Configure automatic retries
axiosRetry(client, {
retries: 3,
retryDelay: axiosRetry.exponentialDelay,
retryCondition: (error) => {
return axiosRetry.isNetworkOrIdempotentRequestError(error) ||
error.code === 'HPE_UNEXPECTED_EOF';
}
});
// Make request
try {
const response = await client.get('https://api.example.com/data');
console.log(response.data);
} catch (error) {
console.error('Request failed after retries:', error.message);
}Or using got with built-in retry:
const got = require('got');
try {
const response = await got('https://api.example.com/data', {
timeout: {
request: 30000
},
retry: {
limit: 3,
methods: ['GET', 'POST'],
statusCodes: [408, 413, 429, 500, 502, 503, 504],
errorCodes: [
'ETIMEDOUT',
'ECONNRESET',
'EADDRINUSE',
'ECONNREFUSED',
'EPIPE',
'ENOTFOUND',
'ENETUNREACH',
'EAI_AGAIN',
'HPE_UNEXPECTED_EOF'
]
}
});
console.log(response.body);
} catch (error) {
console.error('Request failed:', error.message);
}If you control the server, ensure responses are properly formed and connections aren't closed prematurely:
const http = require('http');
const server = http.createServer((req, res) => {
// Always set appropriate headers
res.setHeader('Content-Type', 'application/json');
const responseData = JSON.stringify({ message: 'Success' });
// Explicitly set Content-Length
res.setHeader('Content-Length', Buffer.byteLength(responseData));
// Ensure complete response is sent before ending
res.writeHead(200);
res.end(responseData); // Use end() with data, not separate write() + end()
});
// Handle server errors
server.on('error', (err) => {
console.error('Server error:', err);
});
// Handle client errors
server.on('clientError', (err, socket) => {
console.error('Client error:', err);
// Send proper 400 response instead of abrupt close
if (socket.writable) {
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
} else {
socket.destroy();
}
});
server.listen(3000);Avoid closing sockets prematurely:
// DON'T DO THIS
res.write('Some data');
socket.destroy(); // Abrupt close causes HPE_UNEXPECTED_EOF
// DO THIS INSTEAD
res.write('Some data');
res.end(); // Proper response terminationIf requests pass through proxies, load balancers, or API gateways, verify their timeout configurations:
# Check if proxy is closing connections
curl -v https://api.example.com/endpoint
# Test direct connection bypassing proxy
curl -v --noproxy '*' https://api.example.com/endpointFor nginx reverse proxy, ensure timeouts are appropriately set:
# /etc/nginx/nginx.conf
http {
# Client body timeout
client_body_timeout 60s;
# Client header timeout
client_header_timeout 60s;
# Keepalive timeout
keepalive_timeout 65s;
# Proxy read timeout
proxy_read_timeout 60s;
# Proxy connect timeout
proxy_connect_timeout 60s;
# Proxy send timeout
proxy_send_timeout 60s;
}For AWS Application Load Balancer or API Gateway, verify idle timeout settings are sufficient for your response times.
HTTP Parser Internals: Node.js uses the llhttp parser (previously http-parser), which is a strict implementation of the HTTP specification. The HPE_UNEXPECTED_EOF error originates from the parser state machine when it's in a state expecting more data (based on Content-Length or chunked encoding) but receives a socket close event instead. The error code HPE stands for "HTTP Parser Error."
Keep-Alive Connection Reuse: A common cause of HPE_UNEXPECTED_EOF is keep-alive connection reuse timing issues. If a server closes an idle keep-alive connection at the exact moment a client tries to reuse it for a new request, the client may send the request header but receive EOF when expecting a response. This is often called the "keep-alive race condition." Modern HTTP clients implement connection health checks or conservative connection reuse strategies to mitigate this.
Chunked Transfer Encoding: When responses use chunked transfer encoding (Transfer-Encoding: chunked), the parser expects each chunk to be formatted as hex-size + CRLF + data + CRLF, terminated by a zero-length chunk (0\r\n\r\n). If the connection closes before this final terminator, HPE_UNEXPECTED_EOF occurs. Ensure your server properly terminates chunked responses.
Content-Length Mismatches: If a server sends a Content-Length header indicating 1000 bytes but only sends 500 bytes before closing the connection, this error will occur. This can happen due to bugs in response generation logic, especially when dynamically calculating response sizes. Always verify Content-Length matches actual body size, or use chunked encoding for dynamic content.
Debugging with Network Traces: Use tools like Wireshark or tcpdump to capture the TCP stream and examine exactly when the FIN packet is sent. This can reveal whether the server is intentionally closing the connection early or if network infrastructure is terminating it:
# Capture HTTP traffic
sudo tcpdump -i any -s 0 -A 'tcp port 80 or tcp port 443' -w capture.pcap
# Analyze with Wireshark or tshark
tshark -r capture.pcap -Y "http" -VNode.js Agent Connection Pooling: The http.Agent maintains a pool of sockets for reuse. Issues can arise if sockets in the pool become stale. Configure agent socket management:
const agent = new http.Agent({
keepAlive: true,
keepAliveMsecs: 30000,
maxSockets: 50,
maxFreeSockets: 10,
timeout: 60000,
freeSocketTimeout: 30000 // Close free sockets after 30s
});Server-Side Prevention: If you're building an HTTP server, use the 'finish' event to ensure responses are fully written before closing:
res.on('finish', () => {
console.log('Response fully sent');
});
res.on('close', () => {
console.log('Connection closed - may indicate client disconnect');
});Error: Listener already called (once event already fired)
EventEmitter listener already called with once()
Error: EACCES: permission denied, open '/root/file.txt'
EACCES: permission denied
Error: Invalid encoding specified (stream encoding not supported)
How to fix Invalid encoding error in Node.js readable streams
Error: EINVAL: invalid argument, open
EINVAL: invalid argument, open
TypeError: readableLength must be a positive integer (stream config)
TypeError: readableLength must be a positive integer in Node.js streams