This error occurs when Firebase services are temporarily unavailable due to scheduled maintenance, unexpected outages, or network connectivity issues. It's typically a transient error that resolves automatically. The fix involves verifying service status, implementing exponential backoff retry logic, checking network connectivity, and disabling/re-enabling the affected service if necessary.
The "UNAVAILABLE: The service is experiencing an outage" error is a gRPC status code 14 that Firebase returns when the backend service cannot be reached or is temporarily offline. This error is deliberately temporary and intended to signal that clients should retry their operations after a delay. Unlike persistent errors (like authentication failures or quota exceeded), UNAVAILABLE errors occur at the infrastructure level and should resolve automatically once the service recovers. Under the hood, this error originates from Firebase's gRPC-based backend. When your client attempts to communicate with Firebase services (Firestore, Realtime Database, Cloud Functions, etc.), the gRPC connection stream fails with code 14. This can happen during: - Scheduled maintenance windows announced on the Firebase Status Dashboard - Unexpected Google Cloud infrastructure incidents - Network connectivity issues between your application and Google's servers - Client-side network problems (firewall, VPN, ISP issues) - SDK version incompatibilities or misconfiguration The error typically manifests as "UNAVAILABLE: [gRPC message]" with additional context like "No connection established", "read ETIMEDOUT", or "read ECONNRESET". For most cases, the service recovers within minutes, and standard retry logic with exponential backoff handles the recovery automatically.
The first step is always to verify if Firebase is experiencing a known outage:
1. Open [Firebase Status Dashboard](https://status.firebase.google.com/)
2. Check the status of your region and affected services (Firestore, Database, Functions, etc.)
3. Look for any incidents marked as "Investigating" or "Degraded"
4. If there's an active incident:
- Read the incident description
- Check the Updates section for resolution timeline
- Wait for the status to change to "Resolved"
5. Subscribe to status updates by clicking the bell icon to get notifications
If Firebase Status shows all green and you're still experiencing UNAVAILABLE errors, the issue is likely client-side or regional. Proceed to the next steps.
Network connectivity issues are a common cause of UNAVAILABLE errors:
Test your connection:
# Test basic internet connectivity
ping google.com
# Test DNS resolution for Firebase
nslookup firebaseio.com
nslookup firestore.googleapis.com
# Test network latency to Firebase
curl -w "%{time_total}\n" -o /dev/null -s https://firebaseio.comBrowser-based test:
1. Open your browser's Developer Tools (F12)
2. Go to the Network tab
3. Filter for "firebaseio.com" or "firestore.googleapis.com"
4. Try an operation in your app
5. Look for failed requests (shown in red)
6. Check the error: if it's "connection timeout" or "DNS failed", you have network issues
Common network blockers:
- Corporate firewalls blocking Google Cloud IPs
- VPN service blocking Firebase connections
- ISP DNS issues (switch to 8.8.8.8 or 1.1.1.1)
- Security software (Netskope, ZScaler) blocking API calls
- Mobile app firewalls in Android/iOS
If blocked by a firewall, contact your network administrator to whitelist Google Cloud IP ranges.
Standard retry logic automatically handles temporary UNAVAILABLE errors:
JavaScript/Web:
import { getFirestore, collection, getDocs } from "firebase/firestore";
const MAX_RETRIES = 5;
const INITIAL_DELAY_MS = 1000; // Start with 1 second
async function fetchWithRetry(operation, retryCount = 0) {
try {
return await operation();
} catch (error) {
// Check if error is UNAVAILABLE (code 14 or firestore/unavailable)
const isUnavailable =
error.code === "unavailable" ||
error.code === "firestore/unavailable" ||
error.message?.includes("UNAVAILABLE");
if (isUnavailable && retryCount < MAX_RETRIES) {
const delayMs = INITIAL_DELAY_MS * Math.pow(2, retryCount);
console.log(`Retrying in ${delayMs}ms (attempt ${retryCount + 1}/${MAX_RETRIES})`);
// Wait before retrying
await new Promise(resolve => setTimeout(resolve, delayMs));
// Recursively retry with exponential backoff
return fetchWithRetry(operation, retryCount + 1);
} else if (isUnavailable) {
throw new Error(`Service unavailable after ${MAX_RETRIES} retries. Please try again later.`);
}
// Re-throw non-UNAVAILABLE errors
throw error;
}
}
// Usage example:
async function getUsers() {
const db = getFirestore();
const usersRef = collection(db, "users");
return fetchWithRetry(() => getDocs(usersRef));
}
getUsers()
.then(snapshot => console.log("Success:", snapshot.size))
.catch(error => console.error("Failed:", error.message));Node.js/Admin SDK:
const admin = require("firebase-admin");
const MAX_RETRIES = 5;
const INITIAL_DELAY_MS = 1000;
async function firebaseOperationWithRetry(operation, retryCount = 0) {
try {
return await operation();
} catch (error) {
const isUnavailable =
error.code === "unavailable" ||
error.message?.includes("UNAVAILABLE") ||
error.message?.includes("Could not reach Cloud Firestore");
if (isUnavailable && retryCount < MAX_RETRIES) {
const delayMs = INITIAL_DELAY_MS * Math.pow(2, retryCount);
console.log(`Retrying in ${delayMs}ms...`);
await new Promise(resolve => setTimeout(resolve, delayMs));
return firebaseOperationWithRetry(operation, retryCount + 1);
} else if (isUnavailable) {
throw new Error(`Firebase unavailable after ${MAX_RETRIES} retries`);
}
throw error;
}
}
// Usage:
const db = admin.firestore();
firebaseOperationWithRetry(() =>
db.collection("users").doc("user1").get()
)
.then(doc => console.log("User:", doc.data()))
.catch(error => console.error("Error:", error.message));React Native/FlutterFire:
Most Firebase SDKs for mobile include automatic retry logic, but you can enhance it:
// React Native Firebase
import firestore from '@react-native-firebase/firestore';
const getDataWithRetry = async (collectionPath, maxRetries = 5) => {
for (let i = 0; i < maxRetries; i++) {
try {
const snapshot = await firestore()
.collection(collectionPath)
.get()
.timeout(10000); // 10 second timeout
return snapshot;
} catch (error) {
if (error.code === "UNAVAILABLE" && i < maxRetries - 1) {
const delay = 1000 * Math.pow(2, i);
console.log(`Retry ${i + 1}/${maxRetries} after ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
};Key exponential backoff strategy:
- Attempt 1: Wait 1 second
- Attempt 2: Wait 2 seconds
- Attempt 3: Wait 4 seconds
- Attempt 4: Wait 8 seconds
- Attempt 5: Wait 16 seconds
- Stop after 5 retries to avoid infinite loops
Sometimes a fresh service initialization resolves transient UNAVAILABLE errors:
Web/JavaScript:
import { initializeApp } from "firebase/app";
import { getFirestore, terminate } from "firebase/firestore";
const firebaseConfig = { /* your config */ };
const app = initializeApp(firebaseConfig);
let db = getFirestore(app);
// If encountering UNAVAILABLE errors:
async function resetFirebaseConnection() {
try {
console.log("Disconnecting from Firebase...");
await terminate(db);
// Wait 2 seconds for cleanup
await new Promise(resolve => setTimeout(resolve, 2000));
console.log("Reconnecting to Firebase...");
db = getFirestore(app);
// Test the connection
const testRef = collection(db, "__test__");
await getDocs(query(testRef, limit(1)));
console.log("Firebase connection re-established");
} catch (error) {
console.error("Failed to reset Firebase connection:", error);
}
}
// Call this function when you detect UNAVAILABLE errors
await resetFirebaseConnection();Admin SDK:
const admin = require("firebase-admin");
async function resetFirebaseAdmin() {
try {
console.log("Shutting down Firestore instance...");
await admin.firestore().terminate();
await new Promise(resolve => setTimeout(resolve, 2000));
console.log("Reinitializing Firestore...");
// Automatically re-initializes on next operation
const testDoc = await admin.firestore()
.collection("__test__")
.limit(1)
.get();
console.log("Firestore re-initialized successfully");
} catch (error) {
console.error("Reset failed:", error);
}
}Firebase Console (if using web admin):
1. Go to [Firebase Console](https://console.firebase.google.com)
2. Select your project
3. Navigate to Build → Firestore Database (or affected service)
4. If you see an error banner, check the Enable or Create button status
5. Sometimes clicking Create again or refreshing helps reinitialize the service
Outdated SDKs can have bugs causing UNAVAILABLE errors:
Update Firebase SDK:
# Web/Node.js
npm install firebase@latest
# Or for specific packages
npm install firebase-admin@latest
# Check installed version
npm list firebaseVerify version in code:
// Check which version is loaded
import { SDK_VERSION } from "firebase/app";
console.log("Firebase SDK version:", SDK_VERSION);Common SDK version issues:
- Firebase SDK v8 and earlier have known UNAVAILABLE issues on certain networks
- SDK v9+ (modular) has better retry logic and error handling
- Mixing SDK versions (e.g., v8 and v9) causes connection issues
Migration example (v8 → v9+):
// Old (v8) - may cause UNAVAILABLE errors
import firebase from "firebase/app";
import "firebase/firestore";
const db = firebase.firestore();
// New (v9+) - better error handling
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
const app = initializeApp(config);
const db = getFirestore(app);If using Firebase Emulator Suite and getting UNAVAILABLE errors, DNS misconfiguration is often the culprit:
Fix emulator DNS:
# Start emulator with explicit DNS server
firebase emulators:start --import=./exported_data
# If that doesn't work, configure Android emulator DNS:
./emulator @Pixel_4_API_30 -dns-server 8.8.8.8Or in your initialization:
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
projectId: "demo-project",
// Other config...
};
if (process.env.NODE_ENV === "development") {
// When using emulator
connectFirestoreEmulator(db, "localhost", 8080);
// Ensure projectId matches FIREBASE_PROJECT_ID env var
}Docker-based emulator:
If running emulator in Docker, ensure the container can reach your host network:
docker run -p 9000:9000 -p 8080:8080 \
-e FIREBASE_EMULATOR_HOST=localhost:8080 \
firebase/firebase-tools:latest \
emulators:start --import=./dataIf using Admin SDK, credential issues can cause UNAVAILABLE errors:
Verify service account setup:
const admin = require("firebase-admin");
const serviceAccount = require("./path/to/serviceAccountKey.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://your-project.firebaseio.com"
});
// Test connection
admin.firestore()
.collection("__test__")
.doc("test")
.set({ test: true })
.then(() => console.log("Credentials valid"))
.catch(error => {
if (error.message.includes("unavailable")) {
console.error("Service account valid but Firebase unavailable");
} else if (error.message.includes("permission")) {
console.error("Service account lacks permissions");
}
});In Google Cloud Console:
1. Go to [IAM & Admin](https://console.cloud.google.com/iam-admin/iam)
2. Find your service account (e.g., [email protected])
3. Ensure it has:
- Editor role (for full access), or
- Cloud Datastore User (for Firestore only)
4. Click the service account → Keys tab
5. Ensure your private key hasn't expired
6. If expired, create a new JSON key and update your code
Implement comprehensive error logging to track UNAVAILABLE issues:
import { getFirestore, collection, getDocs } from "firebase/firestore";
const errorLog = [];
async function monitoredFirebaseOperation(operationName, operation) {
const startTime = Date.now();
try {
const result = await operation();
console.log(`✓ ${operationName} succeeded in ${Date.now() - startTime}ms`);
return result;
} catch (error) {
const duration = Date.now() - startTime;
const errorRecord = {
timestamp: new Date().toISOString(),
operation: operationName,
errorCode: error.code,
errorMessage: error.message,
duration,
userAgent: navigator.userAgent,
networkStatus: navigator.onLine
};
errorLog.push(errorRecord);
// Log to console with details
console.error(`✗ ${operationName} failed:", {
...errorRecord,
stack: error.stack
});
// Send to error tracking service
if (window.__errorTracker) {
window.__errorTracker.captureException(error, {
contexts: { firebase: errorRecord }
});
}
throw error;
}
}
// Usage:
async function getUsers() {
const db = getFirestore();
return monitoredFirebaseOperation(
"fetchUsers",
() => getDocs(collection(db, "users"))
);
}
// Send error logs to your backend periodically
setInterval(() => {
if (errorLog.length > 0) {
fetch("/api/log-errors", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ errors: errorLog.splice(0) }) // Clear log after sending
}).catch(err => console.error("Failed to send error logs:", err));
}
}, 60000); // Send every minuteThis logging helps identify if the issue is:
- Intermittent (occasional failures) → Likely transient outage
- Consistent (always fails) → Network or configuration issue
- Time-based (fails at specific times) → Maintenance windows or traffic patterns
For production-grade reliability, implement a multi-layered approach:
Circuit breaker pattern prevents cascading failures:
class FirebaseCircuitBreaker {
constructor(maxFailures = 5, resetTimeMs = 60000) {
this.failureCount = 0;
this.maxFailures = maxFailures;
this.resetTimeMs = resetTimeMs;
this.state = "closed"; // closed, open, half-open
this.lastFailureTime = null;
}
async call(operation) {
if (this.state === "open") {
if (Date.now() - this.lastFailureTime > this.resetTimeMs) {
this.state = "half-open";
} else {
throw new Error("Circuit breaker is OPEN. Firebase unavailable.");
}
}
try {
const result = await operation();
this.onSuccess();
return result;
} catch (error) {
this.onFailure(error);
throw error;
}
}
onSuccess() {
this.failureCount = 0;
this.state = "closed";
}
onFailure(error) {
if (error.code === "unavailable") {
this.lastFailureTime = Date.now();
this.failureCount++;
if (this.failureCount >= this.maxFailures) {
this.state = "open";
console.error(`Circuit breaker OPEN after ${this.failureCount} failures`);
}
}
}
}
const breaker = new FirebaseCircuitBreaker();
// Use it:
await breaker.call(() => firebaseOperation());Health check endpoint monitors service status:
expressApp.get("/health/firebase", async (req, res) => {
try {
const startTime = Date.now();
await admin.firestore()
.collection("__health_check__")
.doc("ping")
.get();
const latency = Date.now() - startTime;
res.json({
status: "healthy",
latency: `${latency}ms`,
timestamp: new Date().toISOString()
});
} catch (error) {
if (error.code === "unavailable") {
res.status(503).json({
status: "unavailable",
message: "Firebase service temporarily unavailable",
timestamp: new Date().toISOString()
});
} else {
res.status(500).json({
status: "error",
message: error.message
});
}
}
});Graceful degradation for user-facing errors:
async function fetchUserData(userId) {
try {
// Try to fetch from Firestore with timeout
const snapshot = await Promise.race([
db.collection("users").doc(userId).get(),
new Promise((_, reject) =>
setTimeout(() => reject(new Error("timeout")), 5000)
)
]);
return snapshot.data();
} catch (error) {
if (error.message === "timeout" || error.code === "unavailable") {
// Gracefully fall back to cached data
const cachedData = await getCachedUserData(userId);
if (cachedData) {
console.warn(`Using cached data for user ${userId}`);
return cachedData;
}
// Show user a friendly message
throw new Error("Service temporarily unavailable. Please try again in a moment.");
}
throw error;
}
}Monitoring recommendations:
- Track UNAVAILABLE error frequency and duration
- Alert on-call engineers if error rate exceeds threshold
- Correlate with Firebase Status Dashboard incidents
- Implement feature flags to disable dependent features during outages
- Use canary deployments to detect SDK-related UNAVAILABLE issues early
For mobile apps:
React Native and FlutterFire handle some retries automatically, but ensure:
- Firebase SDKs are updated to latest version
- Network timeout settings are appropriate (e.g., 30s for mobile networks)
- Implement offline-first patterns to gracefully handle disconnections
- Test on real devices and network conditions (3G, 4G, WiFi)
Common misconceptions:
- 'UNAVAILABLE' does NOT mean your quota is exceeded (that's a different error)
- 'UNAVAILABLE' does NOT mean authentication failed (that's "unauthenticated")
- Disabling/re-enabling services in Firebase Console is rarely necessary for transient errors
- VPN/proxy issues are surprisingly common; test without security software
Regional considerations:
Firebase service availability can vary by region. If you're experiencing consistent UNAVAILABLE errors:
1. Check if your app users are in a specific region
2. Verify Firebase's regional status (not just global status)
3. Consider using Cloud CDN or a regional failover strategy
Callable Functions: INTERNAL - Unhandled exception
How to fix "Callable Functions: INTERNAL - Unhandled exception" in Firebase
auth/invalid-hash-algorithm: Hash algorithm doesn't match supported options
How to fix "auth/invalid-hash-algorithm: Hash algorithm doesn't match supported options" in Firebase
Hosting: CORS configuration not set up properly
How to fix CORS configuration in Firebase Hosting
auth/reserved-claims: Custom claims use reserved OIDC claim names
How to fix "reserved claims" error when setting custom claims in Firebase
Callable Functions: UNAUTHENTICATED - Invalid credentials
How to fix "UNAUTHENTICATED - Invalid credentials" in Firebase Callable Functions