◉GEMINI LABJP
●TTS — The three TTS preview models shut down on 12/18 per the deprecations page, not 11/17●CLI 0.68 — Gemini CLI stable is v0.68.0, with v0.69.0-preview.0 (11/10) on the preview channel●GEMINI 2.5 — gemini-2.5-pro and 2.5-flash shut down on the API on 12/1, 16 days left. Move to 3.8 Flash or 3.5 Flash-Lite●NODE 26 — On Node 26, Gemini CLI's timeout settings reportedly stop applying to model requests●OMNI 1.1 — From 12/3, requests to gemini-omni-1.1-flash are routed to 1.2 Flash automatically●NEW — Getting Consistent JSON Through the OpenAI-Compatible Layer, in Three Steps●TTS — The three TTS preview models shut down on 12/18 per the deprecations page, not 11/17●CLI 0.68 — Gemini CLI stable is v0.68.0, with v0.69.0-preview.0 (11/10) on the preview channel●GEMINI 2.5 — gemini-2.5-pro and 2.5-flash shut down on the API on 12/1, 16 days left. Move to 3.8 Flash or 3.5 Flash-Lite●NODE 26 — On Node 26, Gemini CLI's timeout settings reportedly stop applying to model requests●OMNI 1.1 — From 12/3, requests to gemini-omni-1.1-flash are routed to 1.2 Flash automatically●NEW — Getting Consistent JSON Through the OpenAI-Compatible Layer, in Three Steps
Articles/API / SDK
◈ API / SDK/2026-05-07Intermediate

Gemini API DEADLINE_EXCEEDED Errors: Five Things to Check First

When DEADLINE_EXCEEDED suddenly starts spiking on your Gemini API backend, here are the five checks I run first — based on real production debugging.

Gemini API256DEADLINE_EXCEEDEDgRPCtroubleshooting98timeout6

A Gemini API backend that has been humming along for weeks suddenly starts returning DEADLINE_EXCEEDED one morning. I have been there myself, and the first half of that day disappeared into log diving before I figured out what was actually going on. Unlike rate-limit errors that hand you a clear message, this one just whispers "deadline" — and choosing the wrong first step can pull you into a long debugging spiral.

This article walks through the five checks I now run, in the order I actually run them, ranked from least to most invasive. If you go through them top-down, you can usually nail down the real cause before touching any business logic.

What the error really means — at which layer is the deadline being hit?

DEADLINE_EXCEEDED is a gRPC status name that means "no response arrived within the allotted time." The Gemini API maps it to HTTP 504, and how you see it differs slightly depending on the SDK and runtime:

  • Python google-genai: google.genai.errors.ServerError: 504 DEADLINE_EXCEEDED (e.code is 504, e.status is DEADLINE_EXCEEDED)
  • Node.js SDK or fetch over HTTPS: surfaces as Request timed out or AbortError
  • Google AI Studio: shows up as a generic "The request timed out" UI message

One trap is easy to miss: if your retry handler catches google.api_core.exceptions.DeadlineExceeded, it will never fire, because the newer google-genai raises its own exception from google.genai.errors. The logs fill with 504s and not a single retry runs. Print type(e) once and catch what actually arrives, not what you assume.

The crucial split is whether the deadline is being hit on the client side (your code or runtime cut the wait short) or on the server side (Gemini itself didn't finish in time). The fixes differ completely. Bumping a client timeout will not help when the server is genuinely slow on a huge prompt, and vice versa. Almost everything else in this guide builds on getting that distinction right.

Check 1: Measure the server-side time before suspecting your code

The very first thing I do is run the same prompt and the same model in Google AI Studio. AI Studio uses a generous internal timeout, so it shows you the real wall-clock time the server takes to finish.

If your code times out at 60 seconds and AI Studio quietly returns the answer at 90 seconds, the bottleneck is your client. If AI Studio is also slow, or fails outright, the issue is server-side and almost certainly tied to input size or model choice.

To spot this quickly in your own code, drop in a tiny instrumentation wrapper around every Gemini call:

# Diagnostic wrapper: log how long every Gemini call actually takes.
import time
from google import genai
 
client = genai.Client(api_key="YOUR_API_KEY")
 
def measured_generate(model: str, contents):
    start = time.perf_counter()
    try:
        response = client.models.generate_content(model=model, contents=contents)
        elapsed = time.perf_counter() - start
        print(f"[OK] {model} took {elapsed:.2f}s")
        return response
    except Exception as e:
        elapsed = time.perf_counter() - start
        print(f"[ERR] {model} failed at {elapsed:.2f}s: {type(e).__name__}: {e}")
        raise
 
# Expected output (healthy):
# [OK] gemini-2.5-flash took 3.42s

Twenty requests of this kind of log are usually enough to reveal the shape of the problem — for example, "average is around 5 seconds, but a long tail is pinning at 60 seconds and getting cut off."

Check 2: Suspect the input size — long prompts, PDFs, and video

Even when timeouts feel random, careful logs almost always show that they cluster around specific request types. In my experience the usual suspects are:

  • Very long prompts: latency tends to balloon once you cross roughly 300K tokens of context
  • PDFs and Office documents: parse time grows with page count
  • Video: anything more than a few minutes long has highly variable processing time

Inlining huge attachments into every request is a recipe for spiky latency. The cleaner pattern is to upload the file once via the File API and pass a reference at inference time. That separates upload time from inference time and makes the whole system easier to reason about.

# Before: send the PDF inline on every request (slow, repetitive)
with open("report.pdf", "rb") as f:
    pdf_bytes = f.read()
response = client.models.generate_content(
    model="gemini-2.5-pro",
    contents=[
        {"inline_data": {"mime_type": "application/pdf", "data": pdf_bytes}},
        "Summarize the five key findings of this document.",
    ],
)
 
# After: upload once via File API, then reference the file in subsequent calls
uploaded = client.files.upload(file="report.pdf")
response = client.models.generate_content(
    model="gemini-2.5-pro",
    contents=[uploaded, "Summarize the five key findings of this document."],
)
# Expected: subsequent calls referencing the same file are dramatically faster.

A nice side effect: when the same document is queried multiple times, you also avoid re-uploading the same bytes, which trims bandwidth.

Check 3: Override the SDK's default timeout with a realistic value

Default SDK timeouts are easy to overlook. The google-genai Python SDK lets you raise the timeout via HttpOptions. For complex Pro-tier reasoning, a value of 120 to 180 seconds is a sensible floor.

from google import genai
from google.genai import types
 
client = genai.Client(
    api_key="YOUR_API_KEY",
    http_options=types.HttpOptions(timeout=180_000),  # milliseconds (180s)
)
 
response = client.models.generate_content(
    model="gemini-2.5-pro",
    contents="Write a long, structured analysis report.",
)
# Expected: long-running reasoning calls no longer get cut off client-side.

If you are calling the REST API directly from Node.js or an edge runtime, manage the timeout yourself with AbortController. Frameworks sometimes apply aggressive defaults — Cloudflare Workers and Vercel Edge in particular — so being explicit is the safer path:

// Works in Node.js and Edge runtimes alike
const ctrl = new AbortController();
const timeout = setTimeout(() => ctrl.abort(), 180_000); // 180s
 
try {
  const res = await fetch(
    "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro:generateContent",
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-goog-api-key": process.env.GEMINI_API_KEY!,
      },
      body: JSON.stringify({ contents: [{ parts: [{ text: "..." }] }] }),
      signal: ctrl.signal,
    }
  );
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const data = await res.json();
  // Expected: response text lives at data.candidates[0].content.parts[0].text
} finally {
  clearTimeout(timeout);
}

That said, raising the timeout often only delays the symptom rather than fixing it. Pair it with a concurrency cap on your worker pool — the Gemini API rate limiting and quota management guide walks through how I structure that — to keep load from snowballing on the server side.

Check 4: Reconsider the model — Pro vs. Flash

gemini-2.5-pro and the Thinking-tier models genuinely run longer reasoning loops, so their tail latency is higher by design. Routing every workload through Pro is a fast way to make timeouts more frequent. Match the model to the task instead.

In the services I run, the rough split is:

  • Short summaries, classification, extraction: gemini-2.5-flash-lite
  • General text generation, FAQ-style replies: gemini-2.5-flash
  • Long-form structuring, complex reasoning, code generation: gemini-2.5-pro

Pushing everything through Pro inflates both latency and cost. In production I have measured cases where a Flash-suitable workload routed to Pro ran 2–3× slower and cost 5–10× more. If you are firefighting DEADLINE_EXCEEDED, the highest-leverage question is often the simplest one: "does this request actually need Pro?"

Check 5: Implement retries with exponential backoff and jitter

Naively retrying on every DEADLINE_EXCEEDED is one of the fastest ways to make the problem worse — your retries pile onto an already-busy upstream and trigger more deadlines. Always combine exponential backoff (gradually increasing waits) with jitter (a small random offset) so concurrent clients do not retry in lockstep.

import random
import time
from google import genai
from google.genai import errors
 
client = genai.Client(api_key="YOUR_API_KEY")
 
def generate_with_retry(model: str, contents, max_retries: int = 3):
    for attempt in range(max_retries):
        try:
            return client.models.generate_content(model=model, contents=contents)
        except errors.ServerError as e:  # any 5xx, including 504 DEADLINE_EXCEEDED and 503 UNAVAILABLE
            if e.code not in (503, 504) or attempt == max_retries - 1:
                raise
            # Exponential backoff + jitter: ~4s, ~8s, ~16s with 0–1s wobble
            wait = (2 ** (attempt + 2)) + random.random()
            print(f"[retry {attempt + 1}/{max_retries}] {e.code} {e.status} -> waiting {wait:.2f}s")
            time.sleep(wait)
 
# Expected: transient deadlines self-heal and the retry trail shows up in logs.

For the broader question of which errors to retry, how to layer in a circuit breaker, and where to give up, the Gemini API error handling and retry patterns article digs deeper. I would recommend reading it before pushing any retry policy to production.

Still failing? Look one layer deeper

If none of the above moves the needle, the deadline is probably being enforced somewhere other than where you think. Real cases I have hit personally include:

  • An NGINX proxy_read_timeout of 60 seconds in front of the API gateway, cutting requests short
  • Cloudflare Workers running into the CPU-time limit (10 ms on the free plan, 30 seconds by default on paid) rather than a network timeout. Time spent waiting on Gemini does not count as CPU time, so on the free plan it is usually your own JSON shaping or heavy post-processing that trips it. Limits change, so check actual CPU time in the dashboard's observability view
  • An API key that turned out to belong to a different project, taking a slower routing path

Edge runtimes are especially prone to this — the deadline you see in logs is rarely from Gemini itself. Cross-check with the Cloudflare Workers subrequest limit troubleshooting guide and the Gemini API slow response and timeout fix guide to rule out infrastructure-level cutoffs before you go any deeper into application code.

What to do next

If you want to reduce DEADLINE_EXCEEDED for real, start with Check 1 — measuring. Increasing timeouts or piling on retries without a latency distribution in hand only hides the symptom. Wrap your Gemini calls with the timing helper above, capture twenty-four hours of traffic, and look at the shape of that distribution. From there you can usually tell whether the issue lives on the client, the server, or in a specific subset of inputs.

In my own debugging session, the moment I had real measurements I noticed that the top 5% of long PDFs accounted for nearly all the failures — and clearing that one slice of traffic resolved most of the problem within thirty minutes. Measure first, then act on the part of the curve that is actually misbehaving.

Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

◈ API / SDK2026-10-30
An 'API key expired' Error Doesn't Always Mean Expired. Check Which Key Is Actually Sent Before You Renew It
A 400 with API_KEY_INVALID from the Gemini API cannot tell you whether the key expired or something else is wrong. A small diagnostic script checks env-var precedence and stray quotes first, run here against dummy keys.
◈ API / SDK2026-10-30
My ADK Agent Never Ran and Said 'API key expired' — Reading the Reason Code Gave Me a Triage Order
When ADK returns 400 INVALID_ARGUMENT 'API key expired. Please renew the API key.' on every run, check this order before assuming the key expired: read the reason code, compare shell and .env keys by their last four characters, then test the key alone over REST.
◈ API / SDK2026-10-14
When Your config Raises "Extra inputs are not permitted", Check the Spelling of Every Key
If a google-genai call fails with a pydantic ValidationError (extra_forbidden) the moment you pass a config, it is almost always a one-letter typo. Here is the output I reproduced, a small function that checks keys before the call, and how to tell why response.text came back as None.