Netlify Scheduled Functions Debugging Guide 2026
Master scheduled function debugging in Netlify with real error patterns, logging strategies, and production-ready code examples.
TL;DR
Netlify scheduled functions (v2.13+) require proper timezone handling, CloudWatch-style logging, and local testing via netlify dev. Common failures: FUNCTION_INVOCATION_TIMEOUT, timezone mismatches, and missing environment variables. Use structured logging and dry-run deployments before production scheduling.
---
The Scheduled Function Debugging Challenge
Netlify's scheduled functions are powerful—but invisible when they fail. Unlike HTTP functions with immediate feedback, scheduled functions run in the background, making debugging a detective game. This guide covers production patterns we use at StillNotAThing.
Real Error Messages You'll See
Know what you're hunting for:
``` ERROR: Task timed out after 900 seconds Function duration: 945000ms Status: FUNCTION_INVOCATION_TIMEOUT ```
``` CRITICAL ERROR: The cron expression "0 9 * * *" is invalid for timezone "America/Denver" ScheduledFunctionError: Unable to parse schedule configuration ```
``` WARNING: Function completed but logs missing Function execution context lost CloudWatch retention: 24 hours ```
Setting Up Local Testing Environment
First, verify your Netlify CLI version (verify in official docs for latest):
```bash netlify --version ```
Create a test scheduled function:
```javascript // netlify/functions/scheduled-cleanup.js // Deploy with: netlify deploy --prod
import { schedule } from "@netlify/functions";
const handler = async (event) => { // Structured logging for CloudWatch const startTime = Date.now(); const executionId = event.request?.headers?.get("x-execution-id") || "local-test"; console.log(JSON.stringify({ level: "INFO", timestamp: new Date().toISOString(), executionId, event: "scheduled_function_started", schedule: event.cron }));
try { // Your async work here const result = await performCleanup(); console.log(JSON.stringify({ level: "INFO", timestamp: new Date().toISOString(), executionId, event: "scheduled_function_completed", duration: Date.now() - startTime, recordsProcessed: result.count }));
return { statusCode: 200, body: JSON.stringify({ success: true, processed: result.count }) }; } catch (error) { console.log(JSON.stringify({ level: "ERROR", timestamp: new Date().toISOString(), executionId, event: "scheduled_function_failed", error: error.message, stack: error.stack, duration: Date.now() - startTime }));
return { statusCode: 500, body: JSON.stringify({ error: error.message }) }; } };
const performCleanup = async () => { // Simulate work with timeout protection await new Promise(resolve => setTimeout(resolve, 100)); return { count: 5 }; };
// UTC timezone - verify in official docs for timezone options // Common mistake: Using local timezone without explicit specification export const config = { schedule: "0 2 * * *" // 2 AM UTC daily };
export default schedule(handler); ```
Debugging Strategies: The Checklist
1. Timeout Issues (Most Common)
Netlify scheduled functions timeout at 900 seconds (15 minutes). If your function hits this:
```javascript
// Add progress checkpoints
const handler = async (event) => {
const checkpoints = [];
try {
checkpoints.push({ name: "start", time: Date.now() });
// Process in chunks
const items = await fetchItems();
const chunkSize = 10;
for (let i = 0; i < items.length; i += chunkSize) {
const chunk = items.slice(i, i + chunkSize);
await processChunk(chunk);
// Log progress every 30 seconds of work
if (i % (chunkSize * 10) === 0) {
checkpoints.push({
name: processed_${i},
time: Date.now(),
elapsed: Date.now() - checkpoints[0].time
});
console.log(JSON.stringify({ level: "INFO", checkpoints }));
}
}
return { statusCode: 200 };
} catch (error) {
console.error(JSON.stringify({
error: error.message,
checkpoints,
totalTime: Date.now() - checkpoints[0].time
}));
throw error;
}
};
```
2. Timezone Mismatches
```javascript // WRONG: Assumes server timezone export const config = { schedule: "0 9 * * MON" // Runs at 9 AM... where? };
// CORRECT: Explicit UTC with offset handling export const config = { schedule: "0 14 * * MON" // 2 PM UTC = 9 AM EDT when DST active };
// BEST: Use cron-parser validation locally import cronParser from "cron-parser";
const validateSchedule = (expression) => {
try {
const interval = cronParser.parseExpression(expression, {
tz: "UTC" // Always validate against UTC
});
const next = interval.next();
console.log(Next execution: ${next.toISOString()});
} catch (error) {
throw new Error(Invalid cron expression: ${error.message});
}
};
validateSchedule("0 14 * * MON"); ```
3. Local Testing Before Deployment
```bash
Test scheduled function locally
netlify devIn another terminal, trigger manually:
curl http://localhost:8888/.netlify/functions/scheduled-cleanupOr use netlify dev CLI:
netlify functions:invoke scheduled-cleanup --payload '{"test":true}' ```4. Environment Variables in Scheduled Context
```javascript const handler = async (event) => { // Verify env vars are loaded if (!process.env.DATABASE_URL) { throw new Error("DATABASE_URL not set in scheduled function context"); } // Environment vars must be prefixed in netlify.toml // [functions] // node_bundler = "esbuild" // [[env.production.context.functions]] // external_node_modules = ["pg"] }; ```
Monitoring & Alerting Patterns
Since scheduled functions are silent by default:
```javascript
// Add observability layer
const notifyOnFailure = async (error, executionId) => {
// Send to your alerting (Slack, PagerDuty, etc.)
await fetch(process.env.ALERT_WEBHOOK, {
method: "POST",
body: JSON.stringify({
text: Scheduled function failed: ${error.message},
executionId,
timestamp: new Date().toISOString()
})
});
};
```
Check [Netlify Functions documentation](https://docs.netlify.com/functions/overview/) (verify in official docs for current version) for CloudWatch integration.
Related Resources
What am I missing?
This guide is incomplete without your experience. Have you debugged scheduled function issues we didn't cover? Timezone edge cases? Performance optimizations? Please share in the comments—especially if you've found patterns different from what's documented here.
---
Last verified: Netlify CLI latest version — verify in official docs for current release notes.