Debugging Netlify Scheduled Functions in 2026
Production patterns for Netlify Functions with cron triggers. Real error messages, exact setup, and troubleshooting for scheduled tasks.
TL;DR
Netlify Scheduled Functions require proper handler exports, timezone configuration, and CloudWatch log monitoring. Common failures: missing schedule property in netlify.toml, incorrect cron syntax (UTC only), and timeout defaults. Use structured logging and test locally with netlify dev.
---
The Challenge
Scheduled functions on Netlify sound straightforward until your 3 AM job silently fails and you don't discover it for weeks. Unlike HTTP-triggered functions that immediately error in your logs, scheduled functions operate in darkness—no incoming request to trace, no obvious failure point.
This guide covers debugging scheduled functions on Netlify Functions (verify current limits in [official pricing docs](https://www.netlify.com/pricing/)).
Setup: Exact Configuration
netlify.toml
Your function schedule lives here, not in code:
```toml [[functions]] name = "daily-report" schedule = "0 8 * * *" timeout = 60 memory = 1024 ```
Critical details:
schedule uses UTC cron format only—no timezone conversions in netlify.tomlschedule? Function won't execute. No error. Ever.Handler Structure (Production Pattern)
```javascript
// netlify/functions/daily-report.js
export const handler = async (event) => {
// Scheduled function context
const isScheduled = event.isScheduled === true;
const invokedAt = new Date().toISOString();
console.log([daily-report] Invoked at ${invokedAt}, scheduled: ${isScheduled});
try {
// Your business logic
const result = await processReports();
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
success: true,
processed: result.count,
executedAt: invokedAt
})
};
} catch (error) {
// Critical: Always return 500, don't throw
console.error('[daily-report] FAILED:', {
error: error.message,
stack: error.stack,
timestamp: invokedAt
});
return {
statusCode: 500,
body: JSON.stringify({
success: false,
error: error.message,
executedAt: invokedAt
})
};
}
};
```
Real Error Messages You'll See
Error 1: Timeout
``` Error: Task timed out after 10.00 seconds at Timeout._onTimeout [as _callback] (timers.js:135:15) at listOnTimeout (internal/timers.js:1463:15) ```
Fix: Increase timeout in netlify.toml: ```toml [[functions]] name = "heavy-job" schedule = "0 2 * * *" timeout = 120 ```
Error 2: Function Execution Failed (No Details)
``` Function 'scheduled-sync' failed with status 500 No logs available for this execution ```
Cause: Unhandled promise rejection or synchronous throw. Netlify can't capture stderr.
Fix: Wrap everything in try-catch and return proper response objects (see handler above).
Error 3: Cold Start + Database Connection Pool
``` Error: connect ECONNREFUSED 127.0.0.1:5432 at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1141:29) Timeout waiting for available connection in pool ```
Cause: Database connection limits + Netlify serverless environment.
Fix: Use connection pooling with short TTL: ```javascript // Use a singleton pattern with proper cleanup let db;
const getDB = async () => { if (!db) { db = await createPool({ max: 2, // Low for serverless idleTimeoutMillis: 5000, connectionTimeoutMillis: 5000 }); } return db; };
export const handler = async (event) => { const client = await getDB(); try { const result = await client.query('SELECT ...'); return { statusCode: 200, body: JSON.stringify(result) }; } finally { // Don't await—Netlify kills container anyway client.end().catch(() => {}); } }; ```
Debugging Workflow
Step 1: Test Locally
```bash netlify dev ```
Open http://localhost:8888/.netlify/functions/daily-report in browser. This won't trigger the schedule, but it tests the handler.
For schedule testing locally, use a manual trigger (verify current behavior in [Functions docs](https://docs.netlify.com/functions/overview/)):
```javascript // Manual test endpoint export const handler = async (event) => { if (event.httpMethod === 'GET') { // Manual trigger for testing return { statusCode: 200, body: 'Scheduled handler executed' }; } }; ```
Step 2: Check Netlify Logs
Dashboard → Site settings → Functions → Logs tab. These show:
Limitation: Logs purge after 3 days (verify current retention in docs).
Step 3: Add Structured Logging
```javascript const log = (level, message, data = {}) => { console.log(JSON.stringify({ timestamp: new Date().toISOString(), level, function: 'daily-report', message, ...data })); };
export const handler = async (event) => { log('INFO', 'Execution started', { isScheduled: event.isScheduled }); try { const records = await fetchData(); log('INFO', 'Data fetched', { count: records.length }); return { statusCode: 200, body: JSON.stringify({ success: true }) }; } catch (error) { log('ERROR', 'Execution failed', { error: error.message }); return { statusCode: 500, body: JSON.stringify({ error: error.message }) }; } }; ```
Step 4: External Monitoring
Netlify logs aren't persistent. For scheduled jobs, send execution results elsewhere:
```javascript
const reportExecution = async (success, duration, error = null) => {
await fetch('https://your-monitoring.com/api/execution', {
method: 'POST',
headers: { 'Authorization': Bearer ${process.env.MONITOR_KEY} },
body: JSON.stringify({
function: 'daily-report',
success,
duration,
error,
timestamp: new Date().toISOString()
})
}).catch(err => console.error('Monitoring failed:', err));
};
```
Cron Syntax Gotchas
0 8 * * * = 8 AM UTC daily (not your timezone)*/15 * * * * = Every 15 minutes (watch for rate limits)0 0 * * MON = Mondays at midnight UTC@hourly shortcuts—use explicit cron syntaxVerify syntax: [crontab.guru](https://crontab.guru/)
Related Guides
---
What am I missing?
Scheduled function debugging differs from typical serverless work. What's your biggest pain point:
Comment below with your real-world debugging stories. Accurate patterns beat theory.