Error Medic

Firebase Rate Limit, 401/403/503 & Token Errors: Complete Troubleshooting Guide

Fix Firebase rate limit, 401 unauthorized, 403 forbidden, 502/503, and token expired errors with step-by-step diagnostics and proven solutions.

Last updated:
Last verified:
2,390 words
Key Takeaways
  • Firebase rate limiting (HTTP 429 or RESOURCE_EXHAUSTED) is most often triggered by excessive Firestore reads/writes, Realtime Database connections, or Authentication sign-in attempts per project per second — check Firebase Console quotas before assuming a code bug.
  • 401 (Unauthorized) and 403 (Forbidden) errors almost always mean the Firebase ID token is missing, expired (tokens expire after 1 hour), or the Security Rules deny the request — refresh the token client-side and verify Security Rules in the Firebase Emulator before deploying.
  • 502/503 errors are transient Firebase infrastructure issues or client-side network problems; implement exponential backoff with jitter (not a fixed retry interval) and monitor the Firebase Status Dashboard at status.firebase.google.com before escalating.
Fix Approaches Compared
MethodWhen to UseTimeRisk
Force-refresh ID token client-side401 / token expired / firebase invalid token errors after ~1 hour< 5 minLow — non-destructive, safe in production
Rewrite Security Rules + deploy403 Forbidden / firebase unauthorized when token is valid but access denied15–30 minMedium — incorrect rules can lock out all users; test in Emulator first
Implement exponential backoffRate limited (429 / RESOURCE_EXHAUSTED) or intermittent 502/503 errors1–2 hoursLow — improves resilience with no data risk
Restructure Firestore queries / batch writesSustained rate limiting caused by fan-out writes or unbounded queries2–8 hoursMedium — requires data model review and migration planning
Upgrade Firebase Spark to Blaze planHard quota ceilings on free tier causing persistent rate limits< 15 minLow — cost increases but hard limits are raised
Rotate & re-provision service account keysfirebase authentication failed on server-side Admin SDK with stale credentials10–20 minMedium — old key must be revoked; update all env vars atomically
Enable App CheckFirebase connection refused or abuse-driven rate exhaustion from unauthorized clients30–60 minLow-Medium — may break legitimate clients if not enrolled properly

Understanding Firebase Rate Limit, Auth, and Connectivity Errors

Firebase encompasses multiple products — Firestore, Realtime Database, Authentication, Cloud Functions, Storage, and Hosting — each with its own quota and error surface. When you see errors like firebase rate limited, firebase 401, firebase token expired, or firebase connection refused, the root cause and fix differ significantly depending on which layer is failing.

This guide walks through a structured diagnosis path for all major Firebase error categories developers encounter in production.


Step 1: Identify the Error Layer

Before applying any fix, categorize the error:

Authentication errors surface as:

  • Firebase: Error (auth/id-token-expired).
  • Firebase: Error (auth/invalid-user-token).
  • HTTP 401 Unauthorized from Firestore REST API
  • UNAUTHENTICATED gRPC status code

Authorization / Security Rules errors surface as:

  • FirebaseError: Missing or insufficient permissions.
  • HTTP 403 Forbidden
  • PERMISSION_DENIED gRPC status code

Rate limiting errors surface as:

  • HTTP 429 Too Many Requests
  • gRPC RESOURCE_EXHAUSTED
  • FirebaseError: Quota exceeded.
  • Realtime Database: { "error": "too_many_requests" }

Infrastructure / connectivity errors surface as:

  • HTTP 502 Bad Gateway or 503 Service Unavailable
  • firebase timeout / DEADLINE_EXCEEDED
  • firebase connection refused on the Admin SDK

Step 2: Diagnose Authentication Failures (401, token expired, invalid token)

Firebase ID tokens are JWTs with a 1-hour TTL. Any operation performed after token expiry — or with a token from the wrong Firebase project — returns auth/id-token-expired or HTTP 401.

Client-side (Web SDK v9+): The SDK does NOT auto-refresh tokens before every call. You must listen to onAuthStateChanged and call getIdToken(true) to force a refresh:

import { getAuth } from 'firebase/auth';
const auth = getAuth();
auth.currentUser?.getIdToken(/* forceRefresh */ true)
  .then(token => console.log('Refreshed token:', token.substring(0, 20) + '...'))
  .catch(err => console.error('Token refresh failed:', err.code));

Server-side (Admin SDK): If the Admin SDK throws firebase authentication failed or invalid-credential, the service account key is likely stale or the GOOGLE_APPLICATION_CREDENTIALS environment variable points to the wrong file. Verify with:

gcloud auth application-default print-access-token
# or for service account JSON:
jq '.client_email' "$GOOGLE_APPLICATION_CREDENTIALS"

Token decode debugging: Use the Firebase Admin SDK to verify a token without network overhead:

const admin = require('firebase-admin');
admin.auth().verifyIdToken(idToken, /* checkRevoked */ true)
  .then(decoded => console.log('UID:', decoded.uid, 'exp:', new Date(decoded.exp * 1000)))
  .catch(err => console.error('Verification error:', err.code));
// Codes: auth/id-token-expired, auth/id-token-revoked, auth/invalid-id-token

Step 3: Diagnose Permission Denied (403, firebase unauthorized)

A valid token that is denied by Security Rules returns PERMISSION_DENIED — distinct from UNAUTHENTICATED. The most common causes:

  1. Security Rules don't match the request path — a rule for /users/{uid} will not match /users/{uid}/profile.
  2. Custom claims not propagated — claims set via setCustomUserClaims() only appear in tokens issued AFTER the claim update. The user must sign out and back in.
  3. Rules reference request.auth.token.email_verified but the user never verified their email.

Use the Rules Playground in Firebase Console or the Emulator to test rules without deploying:

# Start emulator suite
firebase emulators:start --only firestore,auth

# In a separate terminal, run your integration tests against the emulator
FIRESTORE_EMULATOR_HOST="localhost:8080" \
FIREBASE_AUTH_EMULATOR_HOST="localhost:9099" \
npx jest --testPathPattern="firestore.rules"

Step 4: Diagnose Rate Limiting (429, RESOURCE_EXHAUSTED, rate limited)

Firebase enforces quotas at multiple levels. Key limits to know:

Service Default Limit
Firestore reads 1M/day (Spark); unlimited (Blaze, billed)
Firestore writes 600K/day (Spark)
Firestore documents per second 1 write/sec per document
Authentication sign-ins 100/hour per IP (abuse protection)
Realtime Database concurrent connections 100 (Spark); 200K (Blaze)
Cloud Functions invocations 2M/month (free tier)

Identify which quota is exhausted via Firebase Console → Project Settings → Usage and billing, or via the Cloud Console:

# Check Firestore quota via gcloud
gcloud firestore operations list --project=YOUR_PROJECT_ID

# Monitor real-time writes with the Firebase CLI
firebase firestore:indexes --project=YOUR_PROJECT_ID

Fan-out write anti-pattern — the most common cause of hitting write limits is updating a document that triggers Cloud Functions, which update other documents in a chain. Use batched writes to reduce round-trips:

const batch = db.batch();
batch.set(db.doc('counters/daily'), { count: increment(1) }, { merge: true });
batch.update(db.doc(`users/${uid}`), { lastSeen: serverTimestamp() });
await batch.commit(); // Single atomic operation, not two separate writes

Exponential backoff implementation for rate-limited retries:

async function retryWithBackoff(fn, maxRetries = 5) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (err) {
      const isRetryable = err.code === 'resource-exhausted' ||
                          err.code === 'unavailable' ||
                          err?.status === 429 ||
                          err?.status === 503;
      if (!isRetryable || attempt === maxRetries - 1) throw err;
      const delay = Math.min(1000 * 2 ** attempt + Math.random() * 1000, 30000);
      console.warn(`Retry ${attempt + 1}/${maxRetries} in ${Math.round(delay)}ms`);
      await new Promise(r => setTimeout(r, delay));
    }
  }
}

Step 5: Diagnose 502/503 and Connection Refused

502 Bad Gateway and 503 Service Unavailable from Firebase are almost always transient. Before spending hours debugging:

  1. Check https://status.firebase.google.com — Firebase posts incident reports here within minutes of an outage.
  2. Check your client's network — corporate proxies, VPNs, and firewalls commonly block *.firebaseio.com or firestore.googleapis.com.
  3. Check DNS resolutionnslookup firestore.googleapis.com should return Google IP ranges (74.125.x.x, 142.250.x.x, 172.217.x.x).

firebase connection refused on the Admin SDK usually means:

  • The SDK is running in a sandboxed environment (e.g., Lambda without VPC egress) that blocks outbound TCP to port 443.
  • GOOGLE_APPLICATION_CREDENTIALS is unset, causing the SDK to attempt metadata server access that is not available outside GCP.

Step 6: Validate the Fix

After applying a fix, verify with a targeted test rather than redeploying the full application:

# Test Firestore connectivity and auth end-to-end
curl -X GET \
  "https://firestore.googleapis.com/v1/projects/YOUR_PROJECT_ID/databases/(default)/documents/healthcheck/ping" \
  -H "Authorization: Bearer $(gcloud auth print-access-token)"

# Expected: HTTP 200 with document JSON, or 404 if doc doesn't exist (both confirm connectivity+auth)
# Bad: HTTP 401 (bad token), 403 (rules deny), 429 (rate limited), 503 (service down)

Frequently Asked Questions

bash
#!/usr/bin/env bash
# Firebase Diagnostic Script
# Usage: PROJECT_ID=your-project-id bash firebase-diagnose.sh

set -euo pipefail
PROJECT_ID="${PROJECT_ID:-YOUR_PROJECT_ID}"

echo "=== 1. Check Firebase project access ==="
gcloud projects describe "$PROJECT_ID" --format='value(projectNumber)' 2>&1 || \
  echo "ERROR: Cannot access project. Check gcloud auth and project ID."

echo "=== 2. Verify service account credentials ==="
if [[ -n "${GOOGLE_APPLICATION_CREDENTIALS:-}" ]]; then
  echo "Credential file: $GOOGLE_APPLICATION_CREDENTIALS"
  jq -r '"Email: " + .client_email + "\nProject: " + .project_id' \
    "$GOOGLE_APPLICATION_CREDENTIALS" 2>/dev/null || echo "ERROR: Cannot parse credentials JSON"
else
  echo "GOOGLE_APPLICATION_CREDENTIALS not set — using ADC (Application Default Credentials)"
  gcloud auth application-default print-access-token > /dev/null && \
    echo "ADC token: OK" || echo "ERROR: ADC not configured. Run: gcloud auth application-default login"
fi

echo "=== 3. Test Firestore REST API connectivity ==="
TOKEN=$(gcloud auth print-access-token 2>/dev/null || echo "")
if [[ -n "$TOKEN" ]]; then
  HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
    -H "Authorization: Bearer $TOKEN" \
    "https://firestore.googleapis.com/v1/projects/${PROJECT_ID}/databases/(default)/documents" \
    --max-time 10)
  echo "Firestore REST API status: HTTP $HTTP_STATUS"
  case $HTTP_STATUS in
    200) echo "  OK: Authenticated and authorized" ;;
    401) echo "  FIX: Token invalid or expired. Re-run gcloud auth application-default login" ;;
    403) echo "  FIX: Authenticated but denied. Check Security Rules and IAM permissions" ;;
    429) echo "  FIX: Rate limited. Check quota in Firebase Console" ;;
    503) echo "  FIX: Service unavailable. Check https://status.firebase.google.com" ;;
    *)   echo "  UNKNOWN: Investigate HTTP $HTTP_STATUS" ;;
  esac
fi

echo "=== 4. Check Firebase quota usage ==="
gcloud firestore operations list \
  --project="$PROJECT_ID" \
  --filter="metadata.startTime>$(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-1H +%Y-%m-%dT%H:%M:%SZ)" \
  --format='table(name,metadata.operationType,metadata.progressDocuments.completedWork)' 2>/dev/null || \
  echo "(No recent operations or gcloud firestore not available)"

echo "=== 5. DNS resolution check ==="
for host in firestore.googleapis.com identitytoolkit.googleapis.com securetoken.googleapis.com; do
  IP=$(nslookup "$host" 2>/dev/null | awk '/^Address: / {print $2; exit}' || echo "FAILED")
  echo "  $host -> $IP"
done

echo "=== 6. TCP connectivity to Firebase endpoints ==="
for endpoint in "firestore.googleapis.com:443" "identitytoolkit.googleapis.com:443"; do
  host=$(echo $endpoint | cut -d: -f1)
  port=$(echo $endpoint | cut -d: -f2)
  timeout 5 bash -c "echo >/dev/tcp/$host/$port" 2>/dev/null && \
    echo "  $endpoint: OPEN" || echo "  $endpoint: BLOCKED or TIMEOUT"
done

echo "=== 7. Firebase emulator health (if running) ==="
curl -s "http://localhost:4000/" > /dev/null 2>&1 && \
  echo "  Emulator UI: running at http://localhost:4000" || \
  echo "  Emulator: not running (start with: firebase emulators:start)"

echo "=== Diagnostics complete ==="
echo "If rate limited: check Firebase Console -> Usage and billing"
echo "If 401/403: verify token freshness and Security Rules"
echo "If 502/503: check https://status.firebase.google.com"
E

Error Medic Editorial

Error Medic Editorial is a team of senior DevOps engineers, SREs, and cloud architects with collective experience running production workloads on Firebase, GCP, AWS, and Azure. Our guides are based on real incident post-mortems, not documentation rewrites — every fix has been validated against live Firebase projects running millions of daily active users.

Sources

Related Guides