Netlify Scheduled Functions: Debugging Guide 2026
Debug Netlify scheduled functions with real error patterns, logging strategies, and production-ready examples for indie hackers.
TL;DR
Netlify scheduled functions (v2.14.0+) require explicit error handling and CloudWatch-style logging. Common failures: timezone mismatches, missing IAM permissions, and event payload structure bugs. Use structured logging, test locally with netlify dev, and enable function logs streaming.
The Problem With Scheduled Functions
Scheduled functions in Netlify are deceptively simple until they're not. You deploy a function that runs perfectly in netlify dev, but fails silently in production. No error emails. No visible logs. Just... nothing.
The core issue: scheduled functions execute outside your HTTP request context, meaning traditional debugging tools (browser DevTools, request inspection) don't apply. You're debugging blind without proper logging and monitoring setup.
Real Console Errors You'll See
Here are three actual error patterns developers encounter:
Error #1: Timezone Mismatch ``` Task timed out after 900 seconds Scheduled function 'cleanup' did not execute at expected interval ``` This happens when your cron expression assumes UTC but your function logs suggest a different timezone. Netlify runs all scheduled functions in UTC regardless of your local settings.
Error #2: Missing Event Handler ``` TypeError: Cannot read property 'Records' of undefined at Object.handler (/var/task/scheduled-function.js:15:3) ``` Your function expects AWS Lambda event structure but receives Netlify's event wrapper format.
Error #3: Execution Timeout ``` FunctionError: RequestId: abc123 Process exited before completing request Duration: 900003.45 ms ``` Functions timeout at 900 seconds (15 minutes). Database queries, external API calls without proper timeout handling kill your function.
Production-Ready Debugging Pattern
1. Structured Logging Setup
```javascript // netlify/functions/scheduled-cleanup.js
const logger = { info: (msg, data) => { console.log(JSON.stringify({ timestamp: new Date().toISOString(), level: 'INFO', function: 'scheduled-cleanup', message: msg, context: data, })); }, error: (msg, error, data) => { console.error(JSON.stringify({ timestamp: new Date().toISOString(), level: 'ERROR', function: 'scheduled-cleanup', message: msg, error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, context: data, })); }, };
exports.handler = async (event) => { const startTime = Date.now(); try { logger.info('Scheduled function started', { trigger: event.Records?.[0]?.EventSource || 'unknown', utcTime: new Date().toISOString(), });
// Your cleanup logic here const result = await performCleanup(); const duration = Date.now() - startTime; logger.info('Scheduled function completed', { duration, itemsProcessed: result.count, });
return { statusCode: 200, body: JSON.stringify(result) }; } catch (error) { logger.error('Scheduled function failed', error, { duration: Date.now() - startTime, eventType: event.Records?.[0]?.EventSource, }); throw error; // Critical: re-throw so Netlify logs the failure } };
async function performCleanup() {
// Set explicit timeout for external calls
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 25000); // 25 sec timeout
try {
const response = await fetch('https://api.example.com/cleanup', {
method: 'POST',
signal: controller.signal,
});
if (!response.ok) {
throw new Error(API returned ${response.status});
}
return { count: 42, success: true };
} finally {
clearTimeout(timeoutId);
}
}
```
2. Local Testing Strategy
Verify in official docs: Netlify CLI behavior changes with major version updates.
```bash
Install Netlify CLI v17.0+ (verify current version)
npm install -g netlify-cliTest in local environment
netlify devIn another terminal, trigger function manually
netlify functions:invoke scheduled-cleanup --local ```3. netlify.toml Configuration
```toml [functions] node_bundler = "esbuild" directory = "netlify/functions" [[scheduled_functions]] function = "scheduled-cleanup" cron = "0 2 * * *" # 2 AM UTC daily [[scheduled_functions]] function = "sync-database" cron = "*/30 * * * *" # Every 30 minutes ```
Critical: Cron expressions are always UTC. If you need 2 AM EST, calculate backwards to UTC offset.
4. Monitoring & Alerting
```javascript // Send failure alerts to your observability service
async function notifyOnFailure(error, context) { const payload = { service: 'scheduled-functions', function: context.function, error: error.message, timestamp: new Date().toISOString(), severity: 'critical', }; await fetch(process.env.MONITORING_WEBHOOK, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); } ```
Set MONITORING_WEBHOOK in Netlify environment variables (verify in [official Netlify environment variables docs](https://docs.netlify.com/configure-builds/environment-variables/)).
Debugging Checklist
1. View Live Logs: Use netlify logs --tail --function=scheduled-cleanup
2. Verify Cron Syntax: Test at [crontab.guru](https://crontab.guru) - remember it's UTC
3. Check Event Structure: Log JSON.stringify(event, null, 2) to see actual payload format
4. Timeout Handling: Set AbortController timeouts 60+ seconds before 900s limit
5. Environment Variables: Verify secrets aren't undefined in function scope
6. Cold Starts: First execution may be slower; account for this in timeout calculations
Comparison: Local vs Production
See also: [Netlify environment setup](/?guide=netlify-env) and [function deployment patterns](/?guide=function-deploy).
Local execution with netlify dev simulates the event structure but does not match production timing behavior. Your function might handle one database connection pool size locally but fail at scale in production.
Common Gotchas
What am I missing?
Have you debugged Netlify scheduled functions in production? Share in comments:
Corrections welcomeβaccuracy matters for indie hackers shipping to production.