Appwrite Functions Failing or Timing Out? A Diagnostic Guide
Appwrite Function failing, timing out, or returning empty 500s despite a successful build? A diagnostic table, ordered checks, and fixes mapped to each finding — plus when to escalate.
16 Oct 2025, 18:17 UTC

Your Appwrite Function shows up in the console as failed, hangs until the timeout, or returns a 500/504 with an empty body — while the build log says everything succeeded. That combination almost always means the failure is in one of four places: the entrypoint configuration, the timeout budget, missing environment variables or API key scopes, or a build problem that only surfaces at runtime. This guide walks through the checks in the order that isolates the cause fastest.
Version note: exact default timeout values, environment variable names, and console labels vary across Appwrite releases. Check your server version first — in the console footer or via GET /v1/health/version — and match it against the documentation you're reading.
The recognizable condition
You trigger a function (HTTP endpoint, event, or schedule) and one of these happens:
- The execution appears in the console with status
failedand little or no output. - The execution runs until the configured timeout and is killed.
- The HTTP trigger returns 500 or 504 with an empty body, even though the deployment built successfully.
Cause and diagnostic table
| Symptom | Likely cause | Where to confirm |
|---|---|---|
| Fails instantly, module-not-found or "handler not defined" in logs | Entrypoint path or exported handler name doesn't match the deployed code | Execution stderr in the console |
| Runs long, then dies with no error from your code | Execution exceeds the function's configured timeout (cold start plus slow external calls is the usual trigger) | Compare execution duration in console vs. the timeout setting |
| 401/403 errors when code calls back into the Appwrite API | Function's dynamic API key lacks the required scope, or endpoint/project variables are unset | Auth errors in execution logs |
| Deployment never becomes active, or fails only on the server | Dependency install fails in the build container (architecture, registry access, or package size limits) | Deployment build log, not the execution log |
| No logs at all, or identical deployments behave differently | Platform/executor problem (self-hosted: executor container, Docker socket, internal networking) | Escalate — see criteria below |
Ordered checks
1. Read the execution's stdout and stderr
In the Appwrite console, open the function, go to Executions, and open the failed execution. The response, stdout, and stderr tabs are your primary evidence. A stack trace pointing at your code is an application bug; a module-resolution or handler error points at configuration.
2. Confirm the deployment is active and built
A function can have multiple deployments, but only the active one serves traffic. Check that the deployment you expect is marked active and that its build log completed without errors. Build failures (a package that won't install in the build container, or an archive over the size limit) show up here, not in execution logs.
3. Verify entrypoint and handler name
The runtime loads the file named in your configuration and calls the exported handler. If you renamed main.js to index.js, or changed the export from a default to a named export, the runtime exits immediately with a "handler not defined"-style error. Check appwrite.json (or the function's Settings in the console) against the actual file layout in your deployment archive. This mismatch is the most common cause of instant failures with successful builds.
4. Check the timeout budget
Open the execution details and note the duration, then compare it to the function's configured timeout (Settings). If the duration equals the timeout, you have your diagnosis. Remember that cold starts — the runtime container spinning up — count against the budget, so a function that takes 8 seconds warm can blow a 15-second limit cold. Self-hosted operators can also check the server-level function timeout environment setting (for example _APP_FUNCTIONS_TIMEOUT), which caps what individual functions can request; Cloud users cannot inspect this and should test empirically.
5. Test environment variables by presence, not value
Functions that call back into the Appwrite API need the endpoint and project variables (injected automatically in most versions, but confirm for yours) plus any variables you defined. Add a temporary diagnostic line at the top of your handler:
// Node.js example — log presence/length only, never values
for (const name of ['APPWRITE_FUNCTION_ENDPOINT', 'APPWRITE_FUNCTION_PROJECT_ID', 'MY_API_KEY']) {
const v = process.env[name];
console.log(`${name}: ${v ? `set (len ${v.length})` : 'MISSING'}`);
}Never log full values — a leaked API key in execution logs is visible to anyone with console access. If variables are set but API calls still return 401/403, the issue is scopes: the function's dynamic API key only has the scopes you granted it, so a function trying to write documents without the matching scope will be rejected.
6. Separate infrastructure from application code
Deploy a minimal handler that just returns a string:
export default async ({ req, res, log }) => {
log('hello from minimal handler');
return res.json({ ok: true });
};If this succeeds, your infrastructure is fine and the bug is in your code or its dependencies. If it fails the same way, the problem is configuration or platform-level. For deeper reproduction, run the function locally with the Appwrite CLI's local development tooling or the matching open-runtimes Docker image, using the same entrypoint and environment variables.
Fixes mapped to findings
- Handler/entrypoint mismatch: align the file path and export name in
appwrite.jsonor console settings with the deployed code, redeploy, and activate the new deployment. - Timeout: raise the function timeout if the work legitimately needs it; otherwise move slow external calls off the critical path — queue work via another function or an event, and return early. Reduce cold-start cost by trimming dependencies.
- Missing env vars: define them in the function's Settings and redeploy. For 401/403, add the required scopes to the function's API key configuration.
- Build failures: pin dependency versions, avoid packages requiring native compilation that the build container can't perform, and keep the archive under the size limit. Verify by redeploying and watching the build log.
Escalation criteria
Escalate to Appwrite support (Cloud) or a GitHub issue (self-hosted) when:
- Executions fail with no logs at all, including for the minimal handler.
- On self-hosted, the executor container crashes, can't access the Docker socket, or can't reach the internal network — check
docker psand executor container logs on the host. - Two identical deployments behave differently with the same input.
Before escalating, collect: server version (/v1/health/version), function ID, deployment ID, execution ID, and a redacted copy of the execution and build logs. That bundle turns a vague "it fails" report into something support can act on.
Verifying the fix
After each change, trigger the function and confirm three things: the execution status is completed, the duration is comfortably under the timeout, and the response body matches what the handler returns. Then re-run with realistic input, not just the happy path — many timeout fixes pass on empty input and fail on real payloads.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.