How to fix Error: FST_ERR_HOOK_TIMEOUT: Hook has exceeded the timeout limit in Node.js
The FST_ERR_HOOK_TIMEOUT error occurs when a Fastify hook (onRequest, onSend, etc.) takes longer than the allowed timeout to complete. This error indicates that your hook is either performing long-running operations or is stuck in an infinite loop. Resolving this requires optimizing the hook code or adjusting timeout configuration.
Quick fix
Fixes FST_ERR_HOOK_TIMEOUT
fastify.addHook('onRequest', async (request, reply) => {
// This is an 'onRequest' hook
});
fastify.addHook('preHandler', async (request, reply) => {
// This is a 'preHandler' hook
});- Identify which hook is timing out
- Check for synchronous blocking operations
- Check for infinite loops or missing conditions
- Optimize slow database or API calls
fastify.addHook('onRequest', async (request, reply) => {// This is an 'onRequest' hook});fastify.addHook('preHandler', async (request, reply) => {// This is a 'preHandler' hook});Error: FST_ERR_HOOK_TIMEOUT: Hook has exceeded the timeout limit
What this error means
Fastify enforces timeout limits on hooks to prevent them from hanging indefinitely and blocking request processing. Hooks are middleware-like functions that execute at specific lifecycle points (onRequest, preValidation, preHandler, onSend, etc.). When a hook exceeds its configured timeout threshold without completing, Fastify immediately terminates the hook execution and throws this error. This is a safety mechanism to prevent one slow or broken hook from causing your entire application to hang. The error signals that you have a performance problem or logic bug in your hook implementation that needs to be addressed.
Typical symptoms
- Your application crashes with FST_ERR_HOOK_TIMEOUT when processing requests
- Only certain requests trigger the error (those that go through the problematic hook)
- The error message appears in logs with a timeout message
- Requests to routes using the problematic hook fail or hang
- The error occurs unpredictably or under high load
Common causes
- Your hook is performing a slow synchronous operation (database query, file I/O, CPU-intensive work) without awaiting it
- The hook contains an infinite loop or deadlock condition
- Your hook is making an external API call that takes too long or hangs
- The hook's timeout is set too low for the operation it performs
- A dependency used in the hook is blocking or slow
- The hook is waiting for a resource that never becomes available
How to fix "Error: FST_ERR_HOOK_TIMEOUT: Hook has exceeded the timeout limit"
Look at your error logs to see which hook lifecycle stage is failing:
fastify.addHook('onRequest', async (request, reply) => {
// This is an 'onRequest' hook
});
fastify.addHook('preHandler', async (request, reply) => {
// This is a 'preHandler' hook
});Common hook types:
- onRequest: Very first hook, runs before validation
- preValidation: Before request validation
- preHandler: Before route handler executes
- onSend: After handler, before sending response
- onError: When an error occurs
Add logging to identify which hook is timing out:
fastify.addHook('onRequest', async (request, reply) => {
console.log('onRequest hook starting');
// your code
console.log('onRequest hook completed');
});Ensure your hook is not running synchronous blocking code that takes a long time:
// BAD: Synchronous file operations in a hook
fastify.addHook('preHandler', (request, reply, done) => {
const data = fs.readFileSync('./large-file.txt', 'utf-8');
done();
});
// GOOD: Use async file operations
fastify.addHook('preHandler', async (request, reply) => {
const data = await fs.promises.readFile('./large-file.txt', 'utf-8');
});Common blocking operations to avoid:
- fs.readFileSync() / fs.writeFileSync()
- require() for large modules
- Synchronous for loops over large datasets
- Synchronous encryption/hashing
Review your hook code for logic that might hang:
// BAD: Infinite loop possibility
fastify.addHook('preHandler', async (request, reply) => {
while (true) { // Never exits!
await someOperation();
}
});
// BAD: Waiting for a condition that never happens
fastify.addHook('preHandler', async (request, reply) => {
while (!request.user) {
await new Promise(resolve => setTimeout(resolve, 100));
}
});
// GOOD: Use timeouts with Promise.race()
fastify.addHook('preHandler', async (request, reply) => {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Hook timeout')), 5000)
);
await Promise.race([yourAsyncOperation(), timeout]);
});If your hook makes database or API calls, ensure they complete quickly:
// BAD: No timeout on external call
fastify.addHook('preHandler', async (request, reply) => {
const user = await db.query('SELECT * FROM users');
});
// GOOD: Add timeout and connection pooling
fastify.addHook('preHandler', async (request, reply) => {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 3000); // 3s timeout
try {
const user = await fetch('https://api.example.com/user', {
signal: controller.signal
});
} finally {
clearTimeout(timeout);
}
});
// For databases, ensure connection pooling is configured
const db = knex({
client: 'pg',
connection: {
pool: { min: 2, max: 10 }
}
});If the operation is necessary and takes time, increase the hook timeout in Fastify options:
const fastify = require('fastify')({
requestIdHeader: 'x-request-id',
requestIdLogLabel: 'requestId',
disableRequestLogging: false,
requestTimeout: 30000, // 30 seconds for entire request
});
// For specific hooks, you can't directly configure timeout,
// but you can ensure your operations complete within Fastify's timeout
// Default request timeout is 30 secondsNote: You cannot set per-hook timeouts in Fastify directly. The timeout applies to the entire request. If a hook is slow, you need to optimize it rather than just increase the timeout.
If an operation is legitimately slow, move it outside the critical request path:
// BAD: Heavy processing in a hook blocks the request
fastify.addHook('preHandler', async (request, reply) => {
request.userId = await expensiveUserVerification();
});
// GOOD: Cache the result or defer the work
const userCache = new Map();
fastify.addHook('preHandler', async (request, reply) => {
const cached = userCache.get(request.headers.authorization);
if (cached) {
request.userId = cached;
return;
}
request.userId = 'unknown';
});
// Verify user in background, update cache
setInterval(() => {
updateUserCache();
}, 60000); // Every minuteEnsure your hooks handle errors gracefully:
fastify.addHook('preHandler', async (request, reply) => {
try {
// your hook code
} catch (error) {
// Don't let unhandled errors cause timeouts
fastify.log.error(error);
reply.code(500).send({ error: 'Internal server error' });
}
});
// Or throw and let Fastify's error handler deal with it
fastify.addHook('preHandler', async (request, reply) => {
if (!request.user) {
throw new Error('Unauthorized');
}
});
fastify.setErrorHandler((error, request, reply) => {
fastify.log.error(error);
reply.code(500).send({ error: error.message });
});Add timing instrumentation to measure hook performance:
fastify.addHook('preHandler', async (request, reply) => {
const start = performance.now();
try {
// your hook code
} finally {
const duration = performance.now() - start;
if (duration > 1000) { // Log if over 1 second
fastify.log.warn({ duration }, 'Slow preHandler hook');
}
}
});
// Run your test suite with load testing
npm test
// Use tools like autocannon to load test
npx autocannon -c 10 -d 30 http://localhost:3000Advanced notes
Hook execution order: Fastify hooks execute in a specific order: onRequest → preValidation → preHandler → onSend → onResponse. Each hook must complete before the next starts. A slow hook in an early stage (onRequest) will delay all subsequent processing.
Async vs callback syntax: Fastify hooks can use either async/await or callback (done) style. Async is preferred in modern code, but ensure you return a promise or call done():
// Async style
fastify.addHook('preHandler', async (request, reply) => {
await operation();
});
// Callback style
fastify.addHook('preHandler', (request, reply, done) => {
operation().then(() => done()).catch(done);
});Default timeout: Fastify's default request timeout is 30 seconds (requestTimeout: 30000). All hooks must complete within this window combined.
Per-route hooks: You can also use per-route hooks which execute only for specific routes:
fastify.route({
url: '/slow',
method: 'GET',
onRequest: [slowHook],
handler: (request, reply) => {
reply.send({ ok: true });
}
});Debugging: Enable verbose logging to see hook execution:
const fastify = require('fastify')({
logger: {
level: 'debug'
}
});This will show detailed timing information for hook and route execution.
Frequently asked questions
- What does "FST_ERR_HOOK_TIMEOUT" mean?
- Fastify enforces timeout limits on hooks to prevent them from hanging indefinitely and blocking request processing. ). When a hook exceeds its configured timeout threshold without completing, Fastify immediately terminates the hook execution and throws this error.
- How do I fix "FST_ERR_HOOK_TIMEOUT"?
- The FST_ERR_HOOK_TIMEOUT error occurs when a Fastify hook (onRequest, onSend, etc.) takes longer than the allowed timeout to complete. This error indicates that your hook is either performing long-running operations or is stuck in an infinite loop. Resolving this requires optimizing the hook code or adjusting timeout configuration.
- What causes "FST_ERR_HOOK_TIMEOUT"?
- Your hook is performing a slow synchronous operation (database query, file I/O, CPU-intensive work) without awaiting it The hook contains an infinite loop or deadlock condition Your hook is making an external API call that takes too long or hangs
- Is the "FST_ERR_HOOK_TIMEOUT" error serious?
- Yes. This is a high-severity error that usually blocks work until it is fixed, but the steps above resolve it.
Official resources & further reading
- 📚Node.js Official Documentation
- 📖Fastify Hooks documentation(Official Docs)
- 📖Fastify Errors reference(Official Docs)
- 📖Node.js Blocking vs Non-Blocking I/O(Official Docs)