This error occurs when an HTTP/2 frame payload exceeds the maximum allowed frame size limit. HTTP/2 enforces strict frame size constraints (default 16KB), and this error indicates the server or client sent a frame that violates these limits, preventing the connection from continuing.
The "HTTP/2 FRAME_SIZE_ERROR" in Node.js indicates that a frame in the HTTP/2 stream has a payload that exceeds the negotiated maximum frame size. HTTP/2 is a binary framing protocol where data is sent in discrete frames with strict size limitations. When either the server or client sends a frame larger than the maximum frame size setting (default 16,384 bytes), the HTTP/2 protocol layer rejects it with a FRAME_SIZE_ERROR. This is a protocol-level violation that causes the connection to be terminated. The error typically appears during: - Large file uploads or downloads - Sending large request/response headers - Streaming data with custom frame sizes - Misconfigured HTTP/2 settings that aren't aligned between client and server - Third-party proxy or load balancer interference with HTTP/2 frames Unlike HTTP/1.1 chunked encoding that allows large payloads, HTTP/2 frames have explicit size limits that must be respected.
HTTP/2 frames have size limits set during the SETTINGS frame exchange. The default maximum frame size is 16,384 bytes (16KB).
Both client and server negotiate frame sizes:
const http2 = require('http2');
// Server advertising its settings
const server = http2.createSecureServer({
// Maximum frame size this server will accept
settings: {
maxFrameSize: 16384 // Default: 16KB
}
});
// Client setting its own maximum frame size
const client = http2.connect('https://api.example.com', {
settings: {
maxFrameSize: 16384
}
});If either side sends a frame larger than the negotiated size, the connection fails with FRAME_SIZE_ERROR.
If you control the server, increase the maxFrameSize setting to accommodate larger payloads:
const http2 = require('http2');
const fs = require('fs');
const options = {
key: fs.readFileSync('private-key.pem'),
cert: fs.readFileSync('certificate.pem'),
settings: {
headerTableSize: 65536,
enablePush: true,
initialWindowSize: 65535,
maxFrameSize: 1048576, // Increase to 1MB (be careful with memory!)
maxConcurrentStreams: 100,
maxHeaderListSize: 8192
}
};
const server = http2.createSecureServer(options, (req, res) => {
res.writeHead(200, { 'content-type': 'application/json' });
res.end(JSON.stringify({ message: 'Hello from HTTP/2' }));
});
server.listen(443, () => {
console.log('HTTP/2 server listening on https://localhost:443');
});Important: Increasing frame size uses more memory. Start conservatively (32KB or 64KB) and monitor resource usage.
If making HTTP/2 requests to a server you don't control, increase your client's max frame size:
const http2 = require('http2');
const client = http2.connect('https://api.example.com', {
settings: {
maxFrameSize: 1048576 // Increase to 1MB
}
});
const req = client.request({
':path': '/large-file',
':method': 'GET'
});
req.on('response', (headers) => {
console.log('Status:', headers[':status']);
});
req.on('data', (chunk) => {
console.log('Received chunk:', chunk.length, 'bytes');
});
req.on('end', () => {
client.close();
});
req.on('error', (err) => {
if (err.code === 'FRAME_SIZE_ERROR') {
console.error('Frame size exceeded. Server may not support large frames.');
}
client.close();
});Or with the request/client library:
const https = require('https');
const Agent = require('agentkeepalive').HttpsAgent;
const agentOptions = {
maxFrameSize: 1048576
};
const options = {
hostname: 'api.example.com',
path: '/large-file',
agent: new Agent(agentOptions)
};
https.get(options, (res) => {
// Handle response
});Instead of increasing frame size, reduce the data sent in individual frames:
For request payloads:
const http2 = require('http2');
const fs = require('fs');
const client = http2.connect('https://api.example.com');
// Stream large file in chunks rather than all at once
const fileStream = fs.createReadStream('large-file.bin', {
highWaterMark: 16384 // Read in 16KB chunks
});
const req = client.request({
':method': 'POST',
':path': '/upload'
});
fileStream.pipe(req);
req.on('response', (headers) => {
console.log('Upload status:', headers[':status']);
});For response payloads:
const http2 = require('http2');
const zlib = require('zlib');
const server = http2.createSecureServer(options, (req, res) => {
// Compress response headers to reduce frame size
const gzip = zlib.createGzip();
res.writeHead(200, {
'content-type': 'application/json',
'content-encoding': 'gzip'
});
// Stream data in smaller chunks
process.stdout.pipe(gzip).pipe(res);
});Large headers (especially cookies) can exceed frame size. Compress and minimize headers:
const http2 = require('http2');
const client = http2.connect('https://api.example.com', {
settings: {
maxHeaderListSize: 8192 // Limit header list size
}
});
// Check header sizes before sending
function checkHeaderSize(headers) {
let totalSize = 0;
for (const [key, value] of Object.entries(headers)) {
totalSize += key.length + value.toString().length;
}
console.log('Header size:', totalSize, 'bytes');
if (totalSize > 16384) {
console.warn('Headers may exceed frame size!');
}
return totalSize;
}
const headers = {
':path': '/api/data',
':method': 'GET',
'cookie': 'sessionid=...', // Minimize cookies
'user-agent': 'MyApp/1.0'
};
checkHeaderSize(headers);
const req = client.request(headers);Tips:
- Remove unnecessary cookies
- Compress large header values
- Use HTTP/2 header compression (built-in with HPACK)
- Split cookies across multiple requests if needed
Add error handlers to catch FRAME_SIZE_ERROR early:
const http2 = require('http2');
const client = http2.connect('https://api.example.com');
// Listen for session errors
client.on('error', (err) => {
console.error('Session error:', err.code, err.message);
if (err.code === 'FRAME_SIZE_ERROR') {
console.error('Action: Increase maxFrameSize on client or server');
}
});
// Listen for stream errors
const req = client.request({
':path': '/large-file',
':method': 'GET'
});
req.on('error', (err) => {
console.error('Stream error:', err.code, err.message);
});
// Get current settings
client.on('remoteSettings', (settings) => {
console.log('Remote max frame size:', settings.maxFrameSize);
console.log('Remote max header list size:', settings.maxHeaderListSize);
});
client.on('localSettings', (settings) => {
console.log('Local max frame size:', settings.maxFrameSize);
});Some proxies don't properly handle HTTP/2 frame size negotiations. Test direct connection:
# Test direct connection to origin server
curl --http2 https://api.example.com:8443/large-file --output test.bin
# Test through proxy
curl --http2 --proxy https://proxy.example.com:3128 https://api.example.com/large-file --output test.bin
# Compare sizes and check for errorsIf it works direct but fails through proxy:
1. Check proxy HTTP/2 configuration
2. Verify proxy supports HTTP/2 frame size forwarding
3. Consider bypassing proxy for large transfers
4. Contact proxy administrator about HTTP/2 compliance
const http2 = require('http2');
// Some proxies may require specific settings
const client = http2.connect('https://api.example.com', {
settings: {
maxFrameSize: 16384, // Use default to avoid proxy issues
maxHeaderListSize: 8192
},
// Disable push if proxy has issues
settings: { enablePush: false }
});HTTP/2 Frame Size Negotiation
HTTP/2 uses a SETTINGS frame to negotiate frame sizes. The negotiation happens during connection setup:
1. Server sends initial SETTINGS frame with its max frame size
2. Client receives and acknowledges with SETTINGS ACK
3. Client sends its SETTINGS frame
4. Server acknowledges with SETTINGS ACK
5. Both sides use the more conservative limit
The default maximum is 16,384 bytes (16KB). The maximum possible value is 16,777,215 bytes (~16MB), but increasing it consumes more memory.
Memory Implications
Each HTTP/2 connection maintains a buffer for incoming frames. A larger maxFrameSize means:
- More memory per connection
- Larger temporary buffers
- Higher peak memory usage during transfers
For a server with 1000 concurrent connections:
- At 16KB: ~16MB base
- At 1MB: ~1GB base
- At 16MB: ~16GB base
Scale accordingly for your infrastructure.
Common Causes by Scenario
Large File Upload: Client is sending a file in a single frame. Solution: stream the data or increase server's maxFrameSize.
Large Response Headers: Server sends HTTP response with many or large headers in a single HEADERS frame. Solution: compress headers or increase client's maxHeaderListSize.
Streaming with Custom Frames: Middleware or custom streaming logic sends frames larger than negotiated size. Solution: check middleware configuration or implement proper frame chunking.
Proxy Interference: Some proxies terminate HTTP/2 and re-establish connections without properly forwarding settings. Solution: test direct connection or update proxy configuration.
HPACK Header Compression
HTTP/2 uses HPACK compression for headers. This reduces header size significantly. If headers still exceed limits after compression, the frame is too large. Check the post-compression size:
// The actual frame size includes HPACK-compressed headers
// Original headers might be 1MB, but compress to 10KB
const headers = {
':path': '/api/endpoint',
'authorization': 'Bearer token...',
'content-type': 'application/json'
// Many more headers...
};
// HTTP/2 compresses these using HPACK before sending
// If compressed size > maxFrameSize, error occursTesting Frame Sizes
Use h2load tool to test frame sizes:
# Test with specific frame size
h2load --max-concurrent-streams=1 --rate=1 \
-H "custom-header: very-long-value-repeated-many-times..." \
https://api.example.com/testError: 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