Microsoft Teams Error: 'Gateway Timeout' — Cause, Fix, and Troubleshooting Guide
Fix Microsoft Graph 504 Gateway Timeout for Teams: page large collections, trim with select, split into JSON batch or delta, and avoid deep expand.
- #microsoft-teams
- #troubleshooting
- #errors
- #graph-api
Stuck on this Microsoft Teams error? Get the free incident triage checklist
A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.
What this error means
An HTTP 504 from Microsoft Graph means the request took too long for the gateway to get a response from the backend and it gave up. It usually surfaces on expensive reads: large $expand, unpaged collections pulled in one shot, complex $filter expressions, or a backend that is momentarily slow. Graph typically returns the code timeout (and sometimes the generic UnknownError). Unlike a 503, a 504 is frequently a signal that the request itself is too heavy — the primary fix is to make the query cheaper, not just to retry it.
HTTP/1.1 504 Gateway Timeout
Content-Type: application/json
{
"error": {
"code": "timeout",
"message": "The request timed out.",
"innerError": {
"date": "2026-07-12T14:03:27",
"request-id": "77aa11bb-3c4d-4e5f-8a90-2233ccdd4455",
"client-request-id": "77aa11bb-3c4d-4e5f-8a90-2233ccdd4455"
}
}
}
If a request 504s consistently and not just occasionally, treat that as a design signal: split it up, page it, or trim it before you reach for retries.
How it presents
- Large list reads (all members across many teams, all channels, all messages) return HTTP 504.
- The same endpoint returns quickly for small teams but times out for large ones.
- Requests with deep
$expandchains time out while the equivalent unexpanded call succeeds. - Complex
$filterqueries time out where a simpler filter returns fast. - Retrying the exact same heavy request just times out again.
- Aggregation or reporting jobs that pull everything in one call fail as data volume grows.
Tracing the connection
Time the request and capture the correlation IDs so you can tell “too heavy” from “transiently slow”:
curl -s -o /tmp/body.json -w 'http=%{http_code} time=%{time_total}s\n' -X GET \
"https://graph.microsoft.com/v1.0/teams/TEAM_ID/members" \
-H "Authorization: Bearer $GRAPH_TOKEN"
jq '{code: .error.code, requestId: .error.innerError["request-id"],
clientRequestId: .error.innerError["client-request-id"]}' /tmp/body.json 2>/dev/null
Confirm the query is the problem by reducing the page size and trimming fields — if the small, trimmed version returns fast, the original was simply too heavy:
curl -s -o /dev/null -w 'trimmed http=%{http_code} time=%{time_total}s\n' -X GET \
"https://graph.microsoft.com/v1.0/teams/TEAM_ID/members?\$top=20&\$select=id,displayName,roles" \
-H "Authorization: Bearer $GRAPH_TOKEN"
If you are expanding, test the same call without $expand to isolate the cost of the expansion:
curl -s -o /dev/null -w 'no-expand http=%{http_code} time=%{time_total}s\n' -X GET \
"https://graph.microsoft.com/v1.0/groups/GROUP_ID?\$select=id,displayName" \
-H "Authorization: Bearer $GRAPH_TOKEN"
Network path causes
- Unpaged collection reads. Pulling a large collection without
$top/nextLinkforces the backend to assemble too much at once. - Deep or wide
$expand. Expanding related entities inflates the work per row until it exceeds the gateway timeout. - Complex
$filter. Expensive predicates over large sets take longer than the gateway will wait. - No field trimming. Returning full objects instead of a narrow
$selectmoves and serializes far more data than needed. - Slow backend under load. A momentarily slow dependency pushes an already-heavy query past the timeout.
Remediation steps
Page the collection instead of pulling it all at once. Use $top and follow @odata.nextLink until it is gone:
url="https://graph.microsoft.com/v1.0/teams/TEAM_ID/members?\$top=50&\$select=id,displayName,roles"
while [ -n "$url" ] && [ "$url" != "null" ]; do
page=$(curl -s -X GET "$url" -H "Authorization: Bearer $GRAPH_TOKEN")
echo "$page" | jq -r '.value[] | "\(.displayName)\t\(.roles|join(","))"'
url=$(echo "$page" | jq -r '."@odata.nextLink" // "null"')
done
Trim every response with $select so you only transfer the fields you use, and avoid deep $expand — fetch related entities in a follow-up call or via a batch instead of expanding them inline.
When you need data from many resources, split the work into a JSON batch (up to the documented per-batch request limit) rather than one giant expanded query:
curl -s -X POST "https://graph.microsoft.com/v1.0/\$batch" \
-H "Authorization: Bearer $GRAPH_TOKEN" -H "Content-Type: application/json" \
-d '{
"requests": [
{ "id": "1", "method": "GET", "url": "/teams/TEAM_ID/channels?$select=id,displayName" },
{ "id": "2", "method": "GET", "url": "/teams/TEAM_ID/members?$top=50&$select=id,displayName" }
]
}' | jq '.responses[] | {id, status}'
For change-tracking scenarios, use delta queries so you sync only what changed instead of re-reading the full collection each run. Keep retry-with-backoff as a secondary defense for genuinely transient 504s, but make the query cheaper first — retrying a too-heavy request just times out again.
Keeping the path healthy
- Page everything. Never read large collections unpaged; always set
$topand follownextLink. - Trim with
$select. Request only the fields you use — full-object responses are a common, avoidable cause of timeouts. - Avoid deep
$expand. Fetch related data in a follow-up call or batch instead of expanding inline. - Prefer batch and delta. Split fan-out reads into
$batchand use delta for incremental sync to keep each request cheap. - Do not just retry heavy queries. A consistent 504 is a design signal — reduce the query before adding retries.
- Watch scaling behavior. A call that is fast today can 504 as a team or tenant grows; size your paging for the largest expected volume.
Related connectivity errors
Fixed it? Get 500 Microsoft Teams & DevOps AI prompts — free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.
Did this fix your issue?
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.