Netlify Scheduled Functions Debugging 2026
Master debugging Netlify Functions scheduled triggers. Real error patterns, production patterns, and console solutions for indie hackers.
TL;DR: Netlify scheduled functions (v2.8.0+) require proper timezone setup, explicit error logging, and local simulation via netlify dev. Common fails: missing schedule property in function config, timezone mismatches between deployment and local testing, and insufficient CloudWatch log retention. Use console.error() paired with Netlify's function logs dashboard—not stderr alone.
Why Scheduled Functions Break Silently
Scheduled functions on Netlify execute in UTC by default, but your cron expressions might assume your local timezone. The function runs, completes, but produces no output you can easily inspect. Unlike HTTP-triggered functions with request/response cycles, scheduled functions execute in the background with minimal feedback loops.
Real Console Errors You'll Hit
Here are three errors developers encounter regularly when debugging scheduled functions:
Error 1: Schedule property missing ``` Error: Invalid function configuration. Missing required property: schedule at validateFunctionConfig (netlify-functions/validator.js:42:15) ```
Error 2: Timezone mismatch on execution ``` Warning: Function executed at 2026-01-15T14:32:00Z but schedule expected 2026-01-15T19:32:00Z (EST) Function skipped due to schedule drift > 5 minutes ```
Error 3: Timeout on external API calls ``` NetlifyFunctionError: Task timed out after 900000ms at ScheduledFunctionRunner (runtime:scheduler.ts:187:41) Function: process-daily-digest did not complete within execution window ```
Production-Ready Scheduled Function Pattern
This pattern handles timezone conversion, idempotency, and comprehensive logging:
```javascript // netlify/functions/scheduled-cleanup.js import { schedule } from '@netlify/functions';
const handler = async (event) => {
const executionId = ${Date.now()}-${Math.random().toString(36).substr(2, 9)};
const startTime = Date.now();
console.log([${executionId}] Cleanup job started at ${new Date().toISOString()});
try {
// Verify execution context
if (!process.env.NETLIFY_FUNCTION_PATH) {
throw new Error('Not running in Netlify environment');
}
// Idempotency check - prevent duplicate runs within 2 minute window
const lastRunKey = 'scheduled-cleanup-last-run';
const lastRun = await getFromStore(lastRunKey);
const timeSinceLastRun = Date.now() - (lastRun || 0);
if (timeSinceLastRun < 120000) {
console.warn([${executionId}] Skipping: ran ${timeSinceLastRun}ms ago);
return { statusCode: 200, body: 'Skipped (duplicate)' };
}
// Business logic with timeout wrapper
const result = await executeWithTimeout(
cleanupExpiredSessions(),
{ timeout: 25000, name: 'cleanupExpiredSessions' }
);
// Store execution time
await saveToStore(lastRunKey, Date.now());
const duration = Date.now() - startTime;
console.log([${executionId}] Cleanup completed in ${duration}ms. Cleaned: ${result.count} sessions);
return {
statusCode: 200,
body: JSON.stringify({
success: true,
executionId,
duration,
itemsProcessed: result.count
})
};
} catch (error) {
const duration = Date.now() - startTime;
console.error([${executionId}] Failed after ${duration}ms:, {
message: error.message,
stack: error.stack,
timestamp: new Date().toISOString()
});
// Send alert for critical failures
if (error.message.includes('database')) {
await notifySlack({
channel: '#alerts',
text: Scheduled function failed: ${error.message},
executionId
});
}
return {
statusCode: 500,
body: JSON.stringify({ error: error.message, executionId })
};
}
};
// Utility: execute function with timeout
const executeWithTimeout = (promise, { timeout = 25000, name = 'task' } = {}) => {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(
() => reject(new Error(${name} exceeded ${timeout}ms timeout)),
timeout
)
)
]);
};
// Mock store for idempotency const getFromStore = async (key) => { // Replace with your Redis/Durable Object implementation return null; };
const saveToStore = async (key, value) => { // Replace with your Redis/Durable Object implementation };
const cleanupExpiredSessions = async () => { // Your actual cleanup logic return { count: 42 }; };
const notifySlack = async (payload) => { // Your alert implementation };
// Schedule: every day at 2 AM UTC (use cron format) export const config = { schedule: '0 2 * * *' };
export { handler }; ```
Function config file (netlify/functions/scheduled-cleanup.json or inline config export):
```json
{
"schedule": "0 2 * * *"
}
```
Local Debugging Without Waiting 24 Hours
Use netlify dev with manual invocation:
```bash
Terminal 1: Start dev server
netlify devTerminal 2: Manually trigger scheduled function
curl -X POST http://localhost:8888/.netlify/functions/scheduled-cleanupOr use Netlify CLI (verify version with netlify --version)
netlify functions:invoke scheduled-cleanup --local
```For timezone testing, export a test handler:
```javascript export const testTimezone = async (event) => { const formats = { utc: new Date().toISOString(), local: new Date().toString(), timestamp: Date.now(), timezone: Intl.DateTimeFormat().resolvedOptions().timeZone }; console.log('Timezone check:', formats); return { statusCode: 200, body: JSON.stringify(formats) }; }; ```
Accessing Netlify Function Logs
1. Netlify Dashboard: Site → Functions → Click function name → Logs tab
2. CLI: netlify functions:list then check individual function details
3. Timestamp filter: Add time range in dashboard (verify current UI in [official docs](https://docs.netlify.com/functions/overview/))
Current log retention: 30 days for free tier, verify current limits in [pricing docs](https://www.netlify.com/pricing/)
Cron Expression Gotchas
minute hour day month weekday (UTC always)0 2 * * * = 2 AM UTC every day*/15 * * * * = every 15 minutes@daily syntaxDebugging Checklist
schedule property exists in function configconsole.log() output in Netlify dashboard (not local stderr)netlify functions:invoke --localRelated Resources
What am I missing?
Have you debugged scheduled functions differently? Found undocumented behaviors with timezones, log retention, or execution context? Drop corrections and patterns in comments—this ecosystem moves fast and real-world gotchas matter.