LEARN · DEBUGGING GUIDE

Debugging Netlify Serverless Function Errors

Netlify serverless functions fail silently more often than you'd think. Here's exactly how to find and fix the root cause when logs are empty or cryptic.

IntermediateCloud8 min read

What this usually means

Netlify serverless functions run in a Lambda-like environment with strict timeouts, cold starts, and limited logs. Most errors stem from uncaught exceptions, missing dependencies, incorrect handler exports, environment variable misconfiguration, or exceeding the 10-second timeout. The platform truncates logs after 1 MB, so verbose logging can actually hide the root cause. Additionally, functions are bundled and deployed from a .netlify/functions directory or from a configured functions folder, so path issues and incorrect file structure are common. Environment variables must be set at the site level (not build) and are not available during build unless explicitly passed. The key is to isolate whether the error occurs at invocation (timeout, memory) or within the handler logic (runtime error).

( 01 )Fast diagnosis

The first ten minutes — establish facts before touching code.

  • 1Run `ntl functions:log <function-name>` in your terminal to tail real-time logs (requires Netlify CLI)
  • 2Check the function's response headers for `x-nf-error` or `x-nf-request-id`; the error header often contains a hint
  • 3Add a top-level try-catch in your handler that logs the error and returns a 500 with the error message: `return { statusCode: 500, body: JSON.stringify({ error: error.message }) }`
  • 4Test locally with `netlify dev` and inspect the console output; the local environment mirrors production closely
  • 5Check the deploy log in Netlify dashboard under Deploys > [deploy] > Function Logs; it shows build-time errors like missing dependencies
( 02 )Where to look

The specific files, logs, configs, and dashboards that usually own this bug.

  • searchNetlify Dashboard > Functions > [function name] > Logs — shows runtime logs (retained for 7 days)
  • searchNetlify CLI output for `netlify dev` — local function logs with stack traces
  • searchDeploy log in Netlify Dashboard (Deploys > [deploy] > Deploy log) — shows bundling errors
  • searchFunction source file — verify exports: `exports.handler = async (event, context) => {}`
  • searchnetlify.toml — check `[functions]` directory and `functions.node_bundler` settings
  • searchEnvironment variables page in Site settings — confirm they are set at the site level (not build only)
  • searchExternal dependency manifest — package.json for any native modules that may not bundle correctly
( 03 )Common root causes

Practical causes, not theory. These are the things you will actually find.

  • warningUncaught exception in async handler — promise rejection not caught
  • warningMissing or incorrect environment variables — no access to process.env at runtime
  • warningFunction timeout — default 10s, exceeded by slow DB calls or external API requests
  • warningWrong handler export — must be `exports.handler` not default export or named differently
  • warningMissing dependencies — npm modules not installed or pruned incorrectly during deploy
  • warningES modules vs CommonJS mismatch — using `import` in a .js file without `"type": "module"`
  • warningFile system or path errors — reading files relative to process.cwd() which is /tmp in Lambda
( 04 )Fix patterns

Concrete fix directions. Pick the one that matches your root cause.

  • buildWrap handler body in try-catch that returns a structured error response with statusCode and body
  • buildSet environment variables under Site > Environment Variables (not Build) and redeploy with a clean cache
  • buildIncrease timeout in netlify.toml: `[functions] directory = "functions" [functions] timeout = 30`
  • buildUse `@netlify/functions` wrapper to handle errors and responses consistently
  • buildBundle dependencies locally with esbuild or ensure package.json has all needed deps in `dependencies` (not devDependencies)
  • buildConvert to CommonJS or set `"type": "module"` and use `.mjs` extension to avoid module system conflicts
( 05 )How to verify

A fix you cannot prove is a guess. Close the loop.

  • verifiedCall the function endpoint with curl and check the response body contains your custom error message
  • verifiedCheck the function logs in the dashboard for the expected log output after the fix
  • verifiedRun `netlify dev` and test locally to confirm the fix works in a simulated environment
  • verifiedDeploy a new version and test with multiple invocations to ensure cold starts work
  • verifiedMonitor the `x-nf-error` header in the response to confirm it's gone
  • verifiedCheck the function's metrics in the dashboard to see if invocation count increases without errors
( 06 )Mistakes to avoid

Things that make this bug worse or harder to find.

  • warningDon't rely solely on the Netlify dashboard logs — they truncate at 1 MB; add structured logging to capture the full error
  • warningDon't set environment variables only in the build step — runtime variables must be set at the site level
  • warningDon't assume local `netlify dev` uses the same environment variables — they are read from .env file, not the dashboard
  • warningDon't forget to redeploy after changing environment variables — they are not live until the next deploy
  • warningDon't use `console.log` excessively — it can fill the log buffer and truncate the actual error
  • warningDon't ignore the function's build output — missing dependencies often show up as 'Cannot find module' in deploy logs
( 07 )War story

The Silent 500: A Netlify Function That Failed Without a Trace

Backend EngineerNetlify Functions, Node.js 18, Express.js, MongoDB Atlas, Serverless Framework

Timeline

  1. 09:15Deploy triggered by git push to main branch
  2. 09:17Deploy succeeds, but /api/users endpoint returns 500
  3. 09:20Check Netlify Functions dashboard — no logs for the function
  4. 09:25Run `ntl functions:log users` — no output
  5. 09:30Add try-catch wrapper and redeploy
  6. 09:33Function now returns 500 with error: "MongoServerSelectionError: connect ECONNREFUSED 127.0.0.1:27017"
  7. 09:35Realize MongoDB connection string uses localhost instead of Atlas URI
  8. 09:36Update environment variable MONGODB_URI in Netlify dashboard
  9. 09:40Redeploy and test — function works

I pushed what I thought was a simple API endpoint for user profiles. The build passed, the site deployed, but every call to `/api/users` returned a 500 with an empty body. No logs in the dashboard, no stack trace. I spent fifteen minutes clicking around the UI, thinking maybe logs were delayed. They weren't.

I added a quick try-catch that returned the error message in the response body. That's when I saw it: `MongoServerSelectionError: connect ECONNREFUSED 127.0.0.1:27017`. My database connection string was pointing to localhost. I had set the `MONGODB_URI` environment variable in my `.env` file for local development, but never added it to the Netlify dashboard. The function was trying to connect to a MongoDB instance on the same machine — which doesn't exist in production.

After adding the correct Atlas URI as a site environment variable and redeploying, the function worked. The lesson: never assume environment variables are set, and always add explicit error handling that surfaces the real error message. The silent 500 is a symptom of poor observability, not a mystery.

Root cause

MongoDB connection string environment variable not set in Netlify dashboard; function used localhost by default.

The fix

Set MONGODB_URI environment variable at Site > Environment Variables and redeploy.

The lesson

Always add a try-catch wrapper that returns error details in the response body, and verify environment variables are set in the production dashboard, not just locally.

( 08 )Understanding Netlify Function Lifecycle and Logging

Netlify functions run on AWS Lambda, but the logging infrastructure is unique. Each invocation produces a log stream that is aggregated and displayed in the dashboard. However, logs are truncated at 1 MB. If your function logs a large object or loops logging, the truncation can cut off the actual error. Use structured logging (e.g., `console.log(JSON.stringify({ level: 'error', message: error.message, stack: error.stack }))`) to keep logs concise.

The function's runtime environment has limited disk space (512 MB /tmp) and no persistent storage. Any file writes during a cold start will be gone on the next invocation. If your function writes to disk, ensure it's idempotent and handles missing files gracefully. Also, the `process.cwd()` is `/var/task`, not the project root, so relative paths to local files (like `./config.json`) will fail.

( 09 )Common Module Resolution and Bundling Issues

Netlify bundles your function code using esbuild by default. This means it resolves dependencies from `node_modules` and bundles them into a single file. If a dependency uses native modules (e.g., `sharp`, `bcrypt`), they may fail to bundle because esbuild cannot compile native addons. In that case, you need to set `[functions] node_bundler = "nft"` in netlify.toml to use the Node.js native bundler, or use a custom build script.

Another common issue is mixing CommonJS and ES modules. If your function file uses `import` but doesn't have `"type": "module"` in package.json, or vice versa, the bundler may throw an error like 'Unexpected token' or 'require is not defined'. The safest approach is to use CommonJS (`require` and `exports.handler`) for Netlify functions, or use `.mjs` extension if you prefer ES modules.

( 10 )Environment Variables: Build vs. Runtime

Netlify has two contexts for environment variables: build and runtime. Build variables are available during the build process (e.g., for setting API keys used in a static site generator). Runtime variables are available in serverless functions. You must set runtime variables in the Site > Environment Variables section. They are not inherited from build variables. Also, variables set in the UI are not automatically available locally; you need to create a `.env` file or use the Netlify CLI to pull them.

A common trap is using `process.env` inside a function that is called during build (e.g., in a Gatsby `gatsby-node.js`). That process runs during build, not in the function runtime. If you need a variable in both contexts, set it in both places or use a prefix like `REACT_APP_` for build and a different name for runtime. Always verify by logging the variable inside the function handler.

( 11 )Timeout and Memory Configuration

The default function timeout is 10 seconds. If your function makes external API calls or database queries that take longer, it will timeout and return a 502 or 504. Increase the timeout in netlify.toml: `[functions] timeout = 30` (max 900 seconds, but Netlify enforces a 10-second default for free plans). Memory defaults to 1024 MB and can be increased up to 3008 MB via `[functions] memory = 2048`.

Cold starts add latency, especially for larger bundles. If your function frequently times out on the first invocation, consider using a warm-up strategy (e.g., a cron job pinging the function every 5 minutes) or reducing bundle size by excluding unnecessary dependencies. Also, avoid loading heavy modules at the top level; lazy load them inside the handler to reduce cold start time.

( 12 )Debugging with Local Emulation and Logs

The `netlify dev` command starts a local server that emulates the Netlify environment. It runs functions using the same Node.js version as your site settings. Use it to test functions before deploying. The local logs appear in the terminal with stack traces. You can also set breakpoints with a debugger by running `netlify dev --inspect` and connecting Chrome DevTools.

For production debugging, use the `x-nf-request-id` header in the response to correlate with logs. You can also enable function insights (paid plan) for detailed metrics. If logs are still empty, check that your function handler is correctly exported and that there are no syntax errors in the function file. A syntax error will cause the function to fail during initialization, and the error will appear in the deploy log, not the function logs.

Frequently asked questions

Why are my Netlify function logs empty even though the function is returning errors?

Empty logs usually mean the function failed before any code executed (e.g., syntax error, missing dependency) or the log buffer was exceeded. Check the deploy log for build-time errors. Add a top-level try-catch to force logging. Also, ensure your function is exporting `handler` correctly.

How do I set environment variables for Netlify functions?

Go to Site > Environment Variables in the Netlify dashboard. Add variables with the exact names your code expects. They become available as `process.env.VAR_NAME` at runtime. Do not prefix them with `GATSBY_` or `REACT_APP_` unless you also need them during build. After setting, redeploy the site.

My function times out after 10 seconds. How do I increase the timeout?

Add to your netlify.toml: `[functions] directory = "functions" timeout = 30` (max 900 seconds). Note that the free plan has a hard limit of 10 seconds, so you may need to upgrade to a paid plan for longer timeouts.

Why does my function work locally with `netlify dev` but fail in production?

Local environment often uses a `.env` file that is not deployed. Also, local paths and dependencies may differ. Use `netlify dev --live` to test with production environment variables. Check that all dependencies are in `dependencies` (not `devDependencies`) and that you don't rely on local files.

How do I handle CORS errors in Netlify functions?

Set the `Access-Control-Allow-Origin` header in your function response. For all origins: `headers: { 'Access-Control-Allow-Origin': '*' }`. If you need to handle preflight OPTIONS requests, add a separate handler or use the `@netlify/functions` middleware to simplify CORS.