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.
Stuck on this? Start guided troubleshooting
Open an interactive diagnostic session with this error already loaded. Work a step-by-step plan, record what each check returns, land on a root cause, and export a clean incident summary — no account needed to start.
Did this fix your issue?
Solved it a different way?
Share the fix that worked for you — reviewed, then published to help the next engineer.
That looks like it may contain a secret (key, token, password, or connection string). Please remove it — a note with a detected secret can’t be published.
Thanks — that helps. Published notes appear after a quick review.
Trending errors this week
The error guides other engineers are actually reading right now.
- 1mount: wrong fs type, bad option, bad superblock
- 2mount: wrong fs type, bad option, bad superblock
- 3Docker 'failed to set up container networking': Fix the Bridge and IP Pool
- 4Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block
- 5Docker 'failed to create shim task': How to Fix the containerd Runtime Error
- 6Too many levels of symbolic links
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.