How to Set Up Google Maps Platform Safely with the gcloud CLI

7 min read
21 views

Google Maps Platform is easy to enable and easy to over-spend on. A leaked browser key, a runaway autocomplete loop, or a forgotten test page can drain hundreds of dollars overnight. This guide walks through the full Google Maps Platform gcloud CLI setup with two-key separation, IP and HTTP referrer restrictions, hard daily quota caps, and budget alerts, so your worst-case bill is bounded by configuration, not trust.

The examples here run on WSL2 Ubuntu in Windows, but the gcloud commands work the same on any Linux or macOS shell.

Prerequisites

  • WSL2 Ubuntu 22.04 or 24.04. Follow How to Install Ubuntu 22.04 in WSL2 on Windows
    if you don’t have it yet.
  • A Google account with a Cloud project. The Maps Platform onboarding flow
    creates one automatically if you sign up via
    console.cloud.google.com.
  • A credit card to add to the billing account. You stay inside the free tier with the caps in this guide, but Google requires a card on file.
  • Comfort with shell tools and CLI-first workflows. If you’ve followed
    How to Install AWS CLI v2 on Ubuntu 22.04, this will feel familiar.

Step 1: Install the gcloud CLI on WSL2 Ubuntu

Google ships an apt repository, which is cleaner than the snap or curl-pipe-bash install on Ubuntu. Run these once.

sudo apt-get update && sudo apt-get install -y apt-transport-https ca-certificates gnupg curl
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg
echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | sudo tee /etc/apt/sources.list.d/google-cloud-sdk.list
sudo apt-get update && sudo apt-get install -y google-cloud-cli

  • apt-transport-https, ca-certificates, gnupg: required to fetch and verify packages over HTTPS.
  • gpg --dearmor: converts Google’s PGP key into the binary format apt expects in /usr/share/keyrings/.
  • google-cloud-cli: the actual gcloud package; pulls in the latest stable SDK.

Authenticate and pick your project:

gcloud init

This opens a browser to sign in, then asks which Cloud project to set as the default. Pick the one that owns your app, not the auto-generated demo project from the Maps Platform onboarding wizard. The rest of this guide assumes a project ID of my-app-project-id; substitute your real value.

Step 2: Link billing to your project

Maps APIs return errors until your project is linked to a billing account. Get your billing account ID:

gcloud billing accounts list

Copy the ACCOUNT_ID column (format XXXXXX-XXXXXX-XXXXXX), then link it:

gcloud billing projects link my-app-project-id \
  --billing-account=XXXXXX-XXXXXX-XXXXXX

Output should show billingEnabled: true. Without this, every Maps API call returns BILLING_DISABLED.

Step 3: Enable only the APIs you actually use

The Maps Platform onboarding wizard auto-enables nearly 30 APIs by default: Aerial View, Pollen, Air Quality, Solar, Street View, Routes, Directions, Distance Matrix, and many more. Every enabled API is attack surface and accidental-charge surface. Disable everything except what your app needs.

For a typical address-search and map-rendering use case, three APIs are enough:

APIService nameUsed for
Maps JavaScript APImaps-backend.googleapis.comInteractive map rendering in the browser
Places API (New)places.googleapis.comAddress autocomplete, Place Details
Geocoding APIgeocoding-backend.googleapis.comFree-text address to coordinates, and reverse

Enable them plus the admin APIs you need for quota and budget management:

gcloud services enable \
  serviceusage.googleapis.com \
  billingbudgets.googleapis.com \
  apikeys.googleapis.com \
  maps-backend.googleapis.com \
  places.googleapis.com \
  geocoding-backend.googleapis.com \
  --project=my-app-project-id

Then list all enabled Maps-related APIs and disable anything you don’t recognise:

gcloud services list --enabled --project=my-app-project-id \
  | grep -iE "maps|places|geocod|directions|street|routes|distance"

To disable an unused API:

gcloud services disable directions-backend.googleapis.com \
  --project=my-app-project-id --force

Step 4: Create two separate API keys

Single-key setups are the most common reason developers wake up to a $5,000 bill. A browser key cannot be IP-restricted because users connect from arbitrary addresses; a server key cannot use HTTP referrer restrictions because servers don’t send a Referer header. Use two keys with different restriction types.

Browser key, HTTP referrer restricted

gcloud services api-keys create \
  --display-name="My App Browser Key (Maps JS)" \
  --project=my-app-project-id \
  --allowed-referrers="https://example.com/*,http://localhost:3000/*,http://localhost:*/*" \
  --api-target=service=maps-backend.googleapis.com

  • --allowed-referrers: only pages served from these origins can use the key. Add your dev domains and your production domain.
  • --api-target: locks the key to Maps JavaScript API. A leaked browser key can’t be used to call Places, Geocoding, or anything else.

The output JSON includes a keyString field. Paste that value into your client-side env var (commonly NEXT_PUBLIC_GOOGLE_MAPS_API_KEY).

Server key, IP restricted

gcloud services api-keys create \
  --display-name="My App Server Key (Places + Geocoding)" \
  --project=my-app-project-id \
  --allowed-ips="203.0.113.0/24" \
  --api-target=service=places.googleapis.com \
  --api-target=service=geocoding-backend.googleapis.com

  • --allowed-ips: only requests from this CIDR can use the key. Use your server’s public IP (a /32) for production. For local development, use your home ISP’s public IP, find it with curl ifconfig.me.
  • --api-target: limits the key to Places and Geocoding only. Place Details and Place Autocomplete both live under places.googleapis.com.

Paste the server key into GOOGLE_MAPS_SERVER_API_KEY (without a NEXT_PUBLIC_ prefix in Next.js, or whatever convention your framework uses for server-only env vars). The server key must never appear in the client bundle.

Dynamic home IPs change. If you want zero maintenance for dev, skip the IP restriction and rely on the daily quota caps in the next step to bound your blast radius.

Step 5: Set hard daily quota caps

Quota caps hard-stop requests. They cannot be bypassed by a bug, a leaked key, or a runaway loop. They are the single most important cost guardrail , more important than budget alerts, which only notify you after the bill is already accruing.

List the available quota metrics for each API so you target the right one:

gcloud alpha services quota list \
  --service=places.googleapis.com \
  --consumer=projects/my-app-project-id \
  --format="value(metric,displayName)"

Set sane starting caps. Tune up later as real traffic justifies:

gcloud alpha services quota update \
  --service=maps-backend.googleapis.com \
  --consumer=projects/my-app-project-id \
  --metric=maps-backend.googleapis.com/billable_default \
  --unit=1/d/{project} --value=2000 --force
gcloud alpha services quota update \
  --service=places.googleapis.com \
  --consumer=projects/my-app-project-id \
  --metric=places.googleapis.com/AutocompletePlacesRequest \
  --unit=1/d/{project} --value=500 --force
gcloud alpha services quota update \
  --service=places.googleapis.com \
  --consumer=projects/my-app-project-id \
  --metric=places.googleapis.com/GetPlaceRequest \
  --unit=1/d/{project} --value=500 --force
gcloud alpha services quota update \
  --service=geocoding-backend.googleapis.com \
  --consumer=projects/my-app-project-id \
  --metric=geocoding-backend.googleapis.com/billable_default \
  --unit=1/d/{project} --value=500 --force

Suggested starting limits for a small app: 2,000 Maps JS loads, 500 Place Autocomplete sessions, 500 Place Details calls, 500 Geocoding requests, all per day. At those numbers, a worst-case full-quota day costs roughly $10,$25 USD before Google’s free monthly tier even applies.

Step 6: Create budget alerts

Budget alerts don’t stop spending, they just email you when thresholds are crossed. Pair them with the quota caps from step 5 so the bill is bounded even if you miss the email.

Get your project number (different from the project ID):

gcloud projects describe my-app-project-id --format="value(projectNumber)"

Create a $10/month budget scoped to that project with three alert thresholds:

gcloud billing budgets create \
  --billing-account=XXXXXX-XXXXXX-XXXXXX \
  --display-name="My App Maps monthly cap" \
  --budget-amount=10USD \
  --threshold-rule=percent=0.10 \
  --threshold-rule=percent=0.50 \
  --threshold-rule=percent=1.00 \
  --threshold-rule=percent=1.00,basis=forecasted-spend \
  --filter-projects=projects/123456789012 \
  --calendar-period=month

  • --budget-amount=10USD: total monthly budget for the alerts (not a hard cap).
  • --threshold-rule=percent=0.10: emails when spend hits 10% of budget ($1).
  • --threshold-rule=percent=0.50: emails at 50% ($5).
  • --threshold-rule=percent=1.00: emails at 100% ($10) on actual spend.
  • --threshold-rule=percent=1.00,basis=forecasted-spend: emails if Google’s spend forecast projects you’ll cross 100% before month end. Good early-warning signal.
  • --filter-projects: scopes the budget to a single project number, even if your billing account spans multiple projects.

Notifications go to anyone with the Billing Account Administrator or Billing Account Costs Manager IAM role on the billing account. Make sure the inbox tied to those roles is one you actually check, not a company shared inbox that might filter Google emails.

Step 7: Verify the setup

Quick sanity checks. Each should return what you just configured.

gcloud billing projects describe my-app-project-id
gcloud services list --enabled --project=my-app-project-id
gcloud services api-keys list --project=my-app-project-id --format=json
gcloud billing budgets list --billing-account=XXXXXX-XXXXXX-XXXXXX

For the API keys, confirm two restriction patterns:

  • Browser key has browserKeyRestrictions.allowedReferrers with your domains, and apiTargets with only maps-backend.googleapis.com.
  • Server key has serverKeyRestrictions.allowedIps with your CIDR, and apiTargets with places.googleapis.com and geocoding-backend.googleapis.com.

If either key has restrictions on the wrong field (a browser key with serverKeyRestrictions, or vice versa), real users will hit 403 errors as soon as their IP doesn’t match. Delete and recreate with the correct flag.

What about session tokens for Places Autocomplete

Places Autocomplete is the typeahead that powers “type a venue name and pin it on the map” features. Google bills it per session when the autocomplete call is followed by a Place Details call sharing the same session token.

A session is one typing-and-selecting cycle. Generate a UUID on first keystroke, send it with every autocomplete request, and send it once more with the Place Details call when the user picks a suggestion. After selection, generate a new UUID for the next session.

Without session tokens, you pay per autocomplete request (one bill per keystroke). With session tokens, you pay one Place Details rate for the whole session. The cost difference is meaningful, similar to the optimisation patterns in How to Avoid Unexpected AWS Lambda Costs.

Minimal browser-side pattern:

const sessionTokenRef = useRef(null);
function ensureSession() {
  if (!sessionTokenRef.current) {
    sessionTokenRef.current = crypto.randomUUID();
  }
  return sessionTokenRef.current;
}
function consumeSession() {
  const t = sessionTokenRef.current;
  sessionTokenRef.current = null;
  return t;
}

Call ensureSession() on every autocomplete request and consumeSession() when the user picks a result and you fetch Place Details.

Conclusion

With billing linked, only the APIs you need enabled, two restricted keys, hard daily quotas, and budget alerts in place, the Google Maps Platform gcloud CLI setup is bounded by configuration rather than trust. The worst-case bill from a leaked key, runaway loop, or bug is capped at single-dollar values before anything in your monitoring even fires.

Next, you might want to review How to Avoid Unexpected AWS Lambda Costs for the same defensive mindset applied to serverless, or How to Install AWS CLI v2 on Ubuntu 22.04 if you’re building a multi-cloud CLI workflow.