Debugging Netlify Scheduled Functions in 2026
Production patterns for Netlify Functions scheduling with real error messages, logging strategies, and timeout solutions.
TL;DR
Netlify Scheduled Functions (using schedule property in function config) require explicit logging setup and timezone awareness. Common failures: timeout errors at 26 seconds, missing netlify.toml configuration, and silent failures from unhandled Promise rejections. Use structured logging, implement retry logic, and test locally with netlify dev.
---
The Scheduled Functions Problem
Scheduled Functions seem simple until they silently fail in production. You deploy a cron job, check back in an hour, and nothing happened—no errors, no logs, nothing. This guide covers the debugging patterns that actually work.
Setup Requirements
First, verify you're on [Netlify Functions v2.0+](https://docs.netlify.com/functions/overview/) (current as of 2026). Your function needs:
```toml
netlify.toml
[functions] node_bundler = "esbuild"[[functions]] function = "scheduled-task" schedule = "@hourly" ```
That schedule property is non-negotiable. Without it, your function won't register as scheduled.
Real Error Messages You'll See
Error 1: Function Timeout
``` Task timed out after 26.01 seconds Error: FUNCTION_TIMEOUT_REACHED ```
Netlify imposes a 26-second hard limit on scheduled function execution (verify in [official limits docs](https://docs.netlify.com/functions/overview/#limits)). Async operations that exceed this will be killed mid-execution.
Fix: Break work into chunks or use background job queues.
Error 2: Missing Handler Export
``` Error: Cannot find handler export in function file Function: scheduled-task Expected: { handler, schedule } exports ```
Your function must export handler as a named export, not default.
Error 3: Silent Failure (Most Dangerous)
No error appears anywhere. The function was triggered but crashed internally:
```javascript // This silently fails - unhandled promise rejection handler = async (event) => { await someAsyncCall(); // If this throws, nothing catches it }; ```
---
Production-Ready Debugging Pattern
1. Structured Logging Setup
```javascript // functions/scheduled-task.js const handler = async (event, context) => { const requestId = context.awsRequestId || crypto.randomUUID(); const logger = { info: (msg, data) => console.log(JSON.stringify({ level: 'INFO', timestamp: new Date().toISOString(), requestId, message: msg, ...data })), error: (msg, err) => console.error(JSON.stringify({ level: 'ERROR', timestamp: new Date().toISOString(), requestId, message: msg, error: err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : err })) };
try { logger.info('Scheduled function triggered', { schedule: '@hourly', timezone: process.env.TZ || 'UTC' });
const result = await performWork(); logger.info('Task completed', { result }); return { statusCode: 200, body: JSON.stringify(result) }; } catch (error) { logger.error('Task failed', error); return { statusCode: 500, body: 'Internal error' }; } };
async function performWork() { // Your logic here return { processed: true }; }
module.exports = { handler, schedule: '@hourly' }; ```
2. Timeout Handling
```javascript const SAFETY_MARGIN_MS = 2000; // 2 second buffer const NETLIFY_TIMEOUT_MS = 26000;
const handler = async (event, context) => { const deadline = Date.now() + NETLIFY_TIMEOUT_MS - SAFETY_MARGIN_MS; return Promise.race([ performWork(), new Promise((_, reject) => setTimeout(() => reject(new Error('Work exceeded time limit')), NETLIFY_TIMEOUT_MS - SAFETY_MARGIN_MS) ) ]).catch(error => { console.error('Timeout or failure:', error.message); // Persist state for retry return { statusCode: 503, body: 'Incomplete - will retry' }; }); }; ```
3. Testing Locally
Netlify's local server doesn't trigger scheduled functions automatically. Simulate manually:
```bash
Terminal 1: Start dev server
netlify devTerminal 2: Trigger function manually
curl -X POST http://localhost:8888/.netlify/functions/scheduled-task ```This won't use the actual scheduler, but reveals most errors:
```javascript // Better: add a manual trigger endpoint during development const handler = async (event, context) => { // Always execute core logic const result = await performWork(); // Manual invocation for testing (remove in production) if (event.httpMethod === 'POST') { return { statusCode: 200, body: JSON.stringify(result) }; } return { statusCode: 204 }; }; ```
---
Timezone & Cron Gotchas
Schedule strings like @hourly, @daily run in UTC by default. If you need different timezones, you must account for this:
```javascript const handler = async (event, context) => { const now = new Date(); const utcHour = now.getUTCHours(); // Execute only during specific UTC hours if (utcHour < 2 || utcHour > 22) { console.log('Outside business hours (UTC)'); return { statusCode: 200, body: 'Skipped' }; } // Process... }; ```
For complex cron expressions, use [cron expression syntax](https://docs.netlify.com/functions/scheduled-functions/) (verify current format in official docs).
---
Observability Checklist
console.log() at function entry, not just errorsawait handler({}, mockContext)---
Related Deep Dives
---
What am I missing?
Have you debugged scheduled functions in production? Share:
Comment below—this guide will be updated with real-world patterns.