Google Maps API 403 Forbidden Error: Complete Troubleshooting Guide
Fix Google Maps API 403 errors fast. Step-by-step guide covering API key restrictions, billing issues, quota limits, and timeout fixes with real commands.
- 403 Forbidden usually means the API key is missing, invalid, or has HTTP referrer/IP restrictions that block your request origin
- Billing not enabled on the Google Cloud project is the second most common cause — even free-tier usage requires an active billing account
- Timeout errors (status UNKNOWN_ERROR or REQUEST_DENIED) often stem from quota exhaustion, network egress rules, or missing service enablement — check Cloud Console quotas first, then verify the Maps JavaScript API and dependent services are all enabled
| Method | When to Use | Time | Risk |
|---|---|---|---|
| Remove/relax key restrictions temporarily | Diagnosing whether a restriction is the root cause | 2 min | Medium — exposes key briefly |
| Add correct HTTP referrer or IP restriction | Production fix after confirming restriction mismatch | 5 min | Low |
| Enable billing on GCP project | Project has no billing account attached | 10 min | Low |
| Enable the specific Maps API service | 403 with 'API not enabled' in error.message | 3 min | Low |
| Rotate API key and update secrets | Suspected key leak or persistent unauthorized use | 15 min | Low |
| Increase or request quota limit | Timeout / OVER_QUERY_LIMIT errors under load | 1–3 days (approval) | Low |
| Implement exponential backoff with retry | Transient timeouts in production traffic | 30 min dev | Low |
Understanding Google Maps API 403 and Timeout Errors
When your application calls any Google Maps Platform API — Maps JavaScript API, Geocoding API, Directions API, Places API — the response can fail in two broad categories: authorization failures (HTTP 403) and operational failures (timeouts, UNKNOWN_ERROR). Understanding which category you are in is the critical first diagnostic step.
What a 403 Response Looks Like
A raw HTTP 403 from the Maps APIs returns a JSON body similar to:
{
"error": {
"code": 403,
"message": "The caller does not have permission",
"status": "PERMISSION_DENIED"
}
}
In the browser console when using the Maps JavaScript API you will see:
Google Maps JavaScript API error: ApiNotActivatedMapError
https://developers.google.com/maps/documentation/javascript/error-messages#api-not-activated-map-error
or:
Google Maps JavaScript API error: RefererNotAllowedMapError
or for server-side APIs called via HTTP:
HTTP 403 {"error":{"code":403,"message":"Requests to this API must be over SSL.","status":"PERMISSION_DENIED"}}
Root Cause Tree for 403
1. API key restrictions mismatch
This is the number-one cause in production. When you create an API key in Google Cloud Console you can restrict it to specific HTTP referrers (for browser keys) or IP addresses (for server keys). If your request origin does not match the allow-list, Google returns 403.
Common mismatches:
- Restriction set to
https://example.com/*but requests come fromhttp://example.com(HTTP vs HTTPS) - Restriction set to
example.com/*but localhost dev traffic hits the key - IP restriction set to a load balancer VIP but the actual egress IP is a NAT gateway
- Wildcard pattern incorrect:
*.example.com/*does not matchexample.com/*
2. Billing not enabled
Every Google Maps Platform project requires an active billing account, even if you stay within the $200/month free credit. A project with no billing account attached returns 403 with status PROJECT_DENIED or BILLING_NOT_ENABLED.
3. The specific API service is not enabled
The Maps JavaScript API, Geocoding API, Directions API, and Places API are separate products. Enabling "Maps JavaScript API" does not automatically enable "Geocoding API". A 403 with message API not enabled means the service was never activated in Cloud Console.
4. Wrong key type
Server-side keys (with IP restrictions) used client-side in the browser — or browser keys used in server-to-server calls — frequently produce 403. Browser keys embedded in server code expose the key without gaining any functionality.
5. HTTP instead of HTTPS
Some Maps APIs require HTTPS. A plain HTTP request returns 403 with Requests to this API must be over SSL.
What Timeout Errors Look Like
Timeout errors manifest differently depending on whether you are using the client-side JS library or server-side HTTP APIs:
Client-side Maps JavaScript API:
map.js:1 Uncaught (in promise) Error: UNKNOWN_ERROR
Server-side HTTP (e.g., Geocoding API):
{"status": "UNKNOWN_ERROR"}
or after hitting quota:
{"status": "OVER_DAILY_LIMIT", "error_message": "You have exceeded your daily request quota for this API."}
A genuine network timeout from your client:
Error: connect ETIMEDOUT 142.251.32.68:443
at TCPConnectWrap.afterConnect [as oncomplete]
Root Cause Tree for Timeouts
1. Quota exhaustion
Google Maps APIs have per-minute and per-day quotas. When exceeded, requests do not timeout in the traditional sense — they return immediately with OVER_QUERY_LIMIT or OVER_DAILY_LIMIT. These are often mistaken for timeouts when wrapped in client code that throws on non-200 status.
2. Network-level timeout (firewall, egress rule, VPC)
Server-side Maps API calls must reach maps.googleapis.com on port 443. Misconfigured VPC egress rules, firewall policies, or overly restrictive NAT gateways can silently drop packets, causing your HTTP client to sit until its own timeout fires.
3. DNS resolution failure
In containerized environments (Kubernetes, Docker), misconfigured DNS can cause maps.googleapis.com to fail to resolve, manifesting as a connection timeout rather than a DNS error.
4. Client-side timeout too short
The default timeout in some HTTP client libraries (e.g., Node.js http.request) is no timeout at all, but wrapper libraries like axios or got often default to 0 ms or a very short value. Under load, slow responses exceed this threshold.
Step 1: Diagnose — Isolate Authorization vs. Network
Run this curl command from the same server or environment where your application runs. Replace YOUR_API_KEY with your actual key:
curl -v "https://maps.googleapis.com/maps/api/geocode/json?address=1600+Amphitheatre+Parkway&key=YOUR_API_KEY"
Interpret the result:
- HTTP 200 with
"status": "OK"→ key is valid, service is enabled, billing is active. Problem is in your application code or referrer restriction. - HTTP 403 or
"status": "REQUEST_DENIED"→ billing, key restriction, or service enablement issue. curl: (6) Could not resolve host→ DNS failure in your environment.curl: (28) Connection timed out→ network/firewall blocking egress to Google."status": "OVER_DAILY_LIMIT"→ quota exhaustion.
Step 2: Fix Authorization Errors (403)
Fix A — Verify and correct key restrictions
- Open Google Cloud Console → APIs & Services → Credentials
- Click your API key → edit
- Under Application restrictions, check the current setting
- For HTTP referrers: add your exact domain with wildcard, e.g.,
https://app.example.com/*ANDhttp://localhost:*/*for dev - For IP addresses: verify the egress IP of your server matches. Find your egress IP:
curl https://checkip.amazonaws.com - Save and wait 5 minutes for propagation
Fix B — Enable billing
- Cloud Console → Billing → Link a billing account to your project
- Confirm in Console → APIs & Services → Dashboard that the Maps API shows a green checkmark
Fix C — Enable the API service
# Using gcloud CLI
gcloud services enable maps-backend.googleapis.com --project=YOUR_PROJECT_ID
gcloud services enable geocoding-backend.googleapis.com --project=YOUR_PROJECT_ID
gcloud services enable directions-backend.googleapis.com --project=YOUR_PROJECT_ID
gcloud services enable places-backend.googleapis.com --project=YOUR_PROJECT_ID
Step 3: Fix Timeout Errors
Fix A — Check and increase quota
- Cloud Console → APIs & Services → [your Maps API] → Quotas
- Identify which quota is hitting its limit (requests per day, requests per minute)
- Click the pencil icon to request an increase — Google typically approves within 24–48 hours
Fix B — Implement exponential backoff
For transient UNKNOWN_ERROR or OVER_QUERY_LIMIT responses, implement retry with backoff in your server-side code:
import time
import requests
def geocode_with_retry(address, api_key, max_retries=5):
base_delay = 0.5
for attempt in range(max_retries):
resp = requests.get(
"https://maps.googleapis.com/maps/api/geocode/json",
params={"address": address, "key": api_key},
timeout=10
)
data = resp.json()
if data["status"] == "OK":
return data
if data["status"] in ("OVER_QUERY_LIMIT", "UNKNOWN_ERROR"):
delay = base_delay * (2 ** attempt)
time.sleep(delay)
continue
raise ValueError(f"Geocoding failed: {data['status']}")
raise RuntimeError("Max retries exceeded")
Fix C — Resolve network/firewall blocking
# Test TCP connectivity to Google's APIs endpoint
nc -zv maps.googleapis.com 443
# Trace route to identify where packets drop
traceroute -T -p 443 maps.googleapis.com
# If inside Kubernetes, test from within the pod
kubectl exec -it <your-pod> -- curl -v https://maps.googleapis.com/maps/api/geocode/json?address=test&key=KEY
If traceroute shows packets dropping at your cloud provider's NAT or firewall layer, update egress rules to allow TCP 443 to 199.36.153.0/24 (Google's API IP range) or use domain-based rules for *.googleapis.com.
Frequently Asked Questions
#!/usr/bin/env bash
# Google Maps API 403 / Timeout Diagnostic Script
# Usage: MAPS_API_KEY=your_key bash diagnose-maps-api.sh
API_KEY="${MAPS_API_KEY:-}"
if [ -z "$API_KEY" ]; then
echo "ERROR: Set MAPS_API_KEY environment variable"
exit 1
fi
echo "=== 1. DNS Resolution ==="
nslookup maps.googleapis.com && echo "DNS OK" || echo "DNS FAILED"
echo ""
echo "=== 2. TCP Connectivity (port 443) ==="
nc -zv -w 5 maps.googleapis.com 443 && echo "TCP OK" || echo "TCP FAILED — check firewall/egress rules"
echo ""
echo "=== 3. TLS Handshake ==="
curl -sv --connect-timeout 10 https://maps.googleapis.com/ -o /dev/null 2>&1 | grep -E "(SSL|TLS|Connected|failed)"
echo ""
echo "=== 4. Geocoding API Auth Check ==="
RESPONSE=$(curl -s --connect-timeout 10 \
"https://maps.googleapis.com/maps/api/geocode/json?address=New+York&key=${API_KEY}")
STATUS=$(echo "$RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('status','NO_STATUS'))" 2>/dev/null || echo "PARSE_ERROR")
echo "API Status: $STATUS"
if echo "$RESPONSE" | grep -q 'error_message'; then
echo "Error message: $(echo "$RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('error_message',''))")"
fi
echo ""
echo "=== 5. Egress IP (must match IP restriction if set) ==="
curl -s --connect-timeout 5 https://checkip.amazonaws.com && echo ""
echo ""
echo "=== 6. Check enabled services (requires gcloud) ==="
if command -v gcloud &>/dev/null; then
PROJECT=$(gcloud config get-value project 2>/dev/null)
echo "Project: $PROJECT"
gcloud services list --enabled --filter="name:googleapis.com" --format="value(name)" 2>/dev/null | grep -i maps || echo "No Maps services found enabled"
else
echo "gcloud not installed — check Cloud Console manually"
fi
echo ""
echo "=== Diagnosis complete ==="
case "$STATUS" in
"OK") echo "RESULT: Key is valid, billing active, service enabled" ;;
"REQUEST_DENIED") echo "RESULT: Key restriction mismatch, billing issue, or service not enabled" ;;
"OVER_DAILY_LIMIT") echo "RESULT: Daily quota exceeded — check billing and quota in Cloud Console" ;;
"OVER_QUERY_LIMIT") echo "RESULT: Rate limit hit — implement exponential backoff" ;;
"UNKNOWN_ERROR") echo "RESULT: Transient Google error or network issue — retry with backoff" ;;
*) echo "RESULT: Unexpected status '$STATUS' — inspect full response" ;;
esacError Medic Editorial
The Error Medic Editorial team consists of senior DevOps engineers, SREs, and cloud architects with combined experience across AWS, GCP, and Azure production environments. We specialize in API integration troubleshooting, infrastructure reliability, and developer tooling. Our guides are tested against live environments before publication.
Sources
- https://developers.google.com/maps/documentation/javascript/error-messages
- https://developers.google.com/maps/faq#over-limit-key-alert
- https://cloud.google.com/maps-platform/billing
- https://stackoverflow.com/questions/14905746/google-maps-api-warning-noapikeys
- https://developers.google.com/maps/documentation/geocoding/requests-geocoding#StatusCodes
- https://cloud.google.com/apis/docs/capping-api-usage