firebase deploy --only hosting blows up mid-run with HTTP Error: 400, Failed to make request to https://firebasehosting.googleapis.com/..., or Site quota exceeded. The Firebase Hosting deploy pipeline is fairly short, so failures cluster into four buckets: CLI version, auth token, firebase.json config, and Spark-plan quota. This article ranks the 5 most common causes by hit rate and walks through a fix path that starts with --debug logs and works down to each root cause.
Common causes
Ordered by hit rate, highest first.
1. CLI version is too old or mismatched with the project
The Firebase CLI changes its API protocol every few months. Old versions (especially below v11) throw vague errors like Unexpected error: ... or Failed to parse host for issues newer CLIs handle cleanly.
$ firebase --version
9.22.0 # too old — recommend 13.x or newer
How to spot it: Errors like Cannot read property 'X' of undefined or Unexpected token in JSON with no recent config changes on your side.
2. Auth token expired or you switched Google accounts
The CLI stores credentials at ~/.config/configstore/firebase-tools.json. When the token expires or your active Google account changes, you get HTTP Error: 401, Request had invalid authentication credentials or Failed to authenticate, have you run firebase login?.
How to spot it: firebase login:list shows an account that doesn’t own this project.
3. firebase.json public path doesn’t match the build output
The public field points at the directory being uploaded. Astro builds to dist/, Next.js static export to out/, Vite to dist/ — but many projects still ship the firebase init default "public": "public" and end up uploading an empty folder.
// firebase.json — typical config for an Astro static site
{
"hosting": {
"public": "dist",
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
"rewrites": [{ "source": "**", "destination": "/index.html" }]
}
}
How to spot it: Deploy log shows i hosting[project]: found 0 files in public — you uploaded nothing.
4. Spark plan quota exhausted
Spark gives each site 10GB outbound bandwidth/day and 360MB storage/month. If a new build pushes cumulative storage past 10GB, you’ll see Site quota exceeded or Hosting site is over its quota.
How to spot it: Firebase Console → Hosting → Usage tab — check the “Storage” and “Data transfer” gauges.
5. Syntax error in rewrites / redirects
Common manual errors: source and destination swapped, ** glob written as *, redirects missing the required type (301/302). The CLI only validates these at deploy time, so the local config “looks fine.”
// Wrong: missing required type field
"redirects": [
{ "source": "/old", "destination": "/new" }
]
// Right
"redirects": [
{ "source": "/old", "destination": "/new", "type": 301 }
]
How to spot it: Log contains Invalid hosting configuration or Specified "redirects" config is not valid.
Shortest path to fix
Pull the full log first, then upgrade / re-auth / config / quota in that order.
Step 1: Re-run with --debug to get the full stack
The default output collapses the real error. Add --debug:
firebase deploy --only hosting --debug 2>&1 | tee firebase-deploy.log
Search the log for [ERROR] or HTTP Error: — the root cause (PERMISSION_DENIED, INVALID_ARGUMENT, QUOTA_EXCEEDED, etc.) is usually nearby.
Step 2: Upgrade the CLI and re-login
# Upgrade globally
npm install -g firebase-tools@latest
firebase --version # expect 13.x or newer
# Refresh the token
firebase logout
firebase login
# Confirm the active account
firebase login:list
firebase projects:list # confirm your project_id is in the list
For CI deploys, use firebase login:ci to mint a token and store it as the FIREBASE_TOKEN env var instead of relying on interactive login.
Step 3: Verify firebase.json public matches your build output
# Check what was actually built
ls -la dist/ # or out/, build/, public/, depending on framework
# Check what firebase.json points at
cat firebase.json | grep public
They must match. Common framework defaults:
| Framework | Build output dir |
|---|---|
| Astro (static) | dist |
| Next.js (static export) | out |
| Vite | dist |
| Create React App | build |
| Vue CLI | dist |
If your CLI version supports it, firebase deploy --only hosting --dry-run validates the config without uploading.
Step 4: Reproduce locally with the emulator
firebase emulators:start --only hosting
# Open http://localhost:5000 — matches production behavior
# rewrites / redirects errors surface here exactly like in deploy
Fix the config, validate in the emulator, then deploy.
Step 5: If quota is the issue
If Step 1’s log shows QUOTA_EXCEEDED:
- Firebase Console → Hosting → Usage to see current numbers.
- Short-term: delete old release versions (Hosting → Release history → pick an old release → Delete) to free storage.
- Long-term: upgrade to the Blaze pay-as-you-go plan. After 10GB you pay ~$0.026/GB transfer and $0.026/GB-month storage — for most small sites still effectively free.
Step 6: Validate rewrites / redirects syntax
Cross-reference firebase.json against the hosting config schema. Or validate locally:
npx ajv-cli validate \
-s https://raw.githubusercontent.com/firebase/firebase-tools/master/schema/firebase-config.json \
-d firebase.json
Prevention
- Pin firebase-tools in
package.json("firebase-tools": "13.15.4") in devDependencies so the whole team / CI runs the same version. - In CI, deploy with a
firebase login:citoken inFIREBASE_TOKENrather than relying on interactive auth. - Use GitHub Actions to run
firebase hosting:channel:deploy previewon PRs — firebase.json bugs surface before merging to main. - Add a pre-deploy step
firebase emulators:exec "echo ok" --only hostingso config errors fail locally. - Set a Firebase Console Budget alert (works even on Spark) to email at 80% of bandwidth or storage quota.