Google Instant Indexer setup, step by step

About 10 minutes: one Google Cloud service account for the Google Indexing API, one key file for IndexNow (Bing, Yandex, Seznam, Naver), three environment variables. Each engine works on its own: you can start with IndexNow only and add Google later.

StepWhereNeeded for
1. Upload the scriptYour PHP hostBoth
2. Project + Indexing APIGoogle Cloud ConsoleGoogle
3. Service account + JSON keyGoogle Cloud ConsoleGoogle
4. Owner in Search ConsoleGoogle Search ConsoleGoogle
5. IndexNow key fileYour site rootIndexNow
6. Environment variablesYour PHP hostBoth
7. Daily queue cronYour server crontabGoogle
8. Check the resultThe script pageBoth
9. Job boards: sitemap, expired jobs, WordPress, APIYour site / CMSGoogle

1. Upload the script

Requirements: PHP 8.0 or newer with the curl, openssl and zlib extensions (almost all hosts have them). On cPanel, check "Select PHP Version" (or "MultiPHP Manager") first: PHP 7.4 or older will not run the script. To check from a shell: php -v and php -m | grep -E 'curl|openssl|zlib'.

Put index.php in its own folder on that host, behind Apache or Nginx+php-fpm. The folder must be writable by PHP: the script keeps its log (gi-log.php) and its queue (gi-queue.php) next to itself, both closed to web visitors. To try it on your computer first:

php -S 0.0.0.0:8080   # then open http://localhost:8080

With nothing configured it runs in demo mode: same flow, no call leaves, every row says so.

2. Create a Google Cloud project and enable the Indexing API

  1. Open console.cloud.google.com/projectcreate, give the project a name (e.g. indexer) and click Create. No billing account is needed.
  2. With that project selected, open APIs & Services > Library, search Web Search Indexing API and click Enable.

3. Create a service account and download its JSON key

  1. Go to IAM & Admin > Service Accounts > Create service account. Any name works; skip the optional role and user steps and click Done.
  2. Copy the service account email, it looks like indexer@your-project.iam.gserviceaccount.com: you need it in step 4.
  3. Open the service account, tab Keys > Add key > Create new key > JSON. A .json file downloads.
  4. Upload that file to your server outside the web root (e.g. /home/you/indexer-key.json): it is a credential, nobody should be able to download it.

If key creation is blocked with "Service account key creation is disabled", your organisation enforces the policy iam.disableServiceAccountKeyCreation: an admin can lift it for this project in IAM & Admin > Organization Policies, or create the project outside the organisation.

4. Add the service account as Owner in Search Console

This is the step people miss. Google accepts URLs only for properties the service account owns: with valid credentials but without this step every Google row answers 403 Permission denied. Failed to verify the URL ownership.

  1. Open Search Console and pick the property that covers your URLs (the Domain property, or the URL-prefix one with the exact https:// and www form you submit).
  2. Go to Settings > Users and permissions, click the ⋮ menu next to your own account (you must be a verified owner) and choose Manage property owners.
  3. Click Add an owner, paste the service account email from step 3 and confirm.

Full permission added from Add user is not enough: it must be Owner. Several sites? Repeat this step on each property with the same service account.

5. Publish the IndexNow key file

IndexNow works for every page of your site and needs no Google account. Pick a key of 8-128 letters, digits or dashes, for example:

php -r 'echo bin2hex(random_bytes(16)), "\n";'

Publish it at the root of each site you submit, as a text file named after the key and containing only the key:

https://your-site.com/<key>.txt   →   <key>

Open that address in a browser: if you see the key, the engines will too.

6. Set the environment variables

VariableValue
GOOGLE_SERVICE_ACCOUNT_JSONAbsolute path to the JSON key from step 3.
GOOGLE_SERVICE_ACCOUNT_JSON_INLINEAlternative: the JSON content itself, for hosts where you cannot place files (use one of the two).
GOOGLE_SERVICE_ACCOUNT_DIRAlternative for more than 200 URLs/day: a folder holding several JSON keys (service accounts from different Google Cloud projects, each Owner in Search Console). When one spends its daily quota the next takes over; the page shows what each has left. Keep this folder, like any key, outside the web root.
INDEXNOW_KEYThe key from step 5.
GI_PASSWORDA password for the page: the browser asks for it (any username). Without it, anyone who finds the page can spend your quota and read your log, and the page shows a warning.
GI_API_TOKENOptional: a long random token that lets your CMS or the WordPress bridge send URLs to the JSON API (step 9). Without it the API is off.
INDEXER_DAILY_QUOTAOptional: only if Google raised your quota above the default 200 URLs/day.

Apache (.htaccess or the virtual host):

SetEnv GOOGLE_SERVICE_ACCOUNT_JSON /home/you/indexer-key.json
SetEnv INDEXNOW_KEY your-key
SetEnv GI_PASSWORD choose-a-long-password

Nginx + php-fpm (in the location ~ \.php$ block):

fastcgi_param GOOGLE_SERVICE_ACCOUNT_JSON /home/you/indexer-key.json;
fastcgi_param INDEXNOW_KEY your-key;
fastcgi_param GI_PASSWORD choose-a-long-password;

Shared hosting or cPanel with no way to set variables: create config.php next to index.php with the same names; an environment variable, when set, wins over the file.

<?php
return [
    'GOOGLE_SERVICE_ACCOUNT_JSON' => '/home/you/private/indexer-key.json',
    'INDEXNOW_KEY' => 'your-key',
    'GI_PASSWORD'  => 'choose-a-long-password',
    'GI_API_TOKEN' => 'a-long-random-token',
];

Local try:

GOOGLE_SERVICE_ACCOUNT_JSON=/home/you/indexer-key.json INDEXNOW_KEY=your-key php -S 0.0.0.0:8080

7. Add the daily queue cron

Google's default quota is 200 URLs/day per project. URLs beyond today's quota, or refused with 429, go to the queue (gi-queue.php), and the page tells you how many are waiting. One cron line sends them every night; cron does not see the web server's variables, so repeat the Google one there:

0 3 * * *  GOOGLE_SERVICE_ACCOUNT_JSON=/home/you/indexer-key.json php /path/to/index.php --process-queue

With config.php the cron needs no variables: php /path/to/index.php --process-queue. URLs that hit a temporary Google error (5xx, timeout) are queued too and retried by the same cron.

Run it as the same user as PHP (e.g. crontab -u www-data -e) so it can write the queue and the log. IndexNow has no queue: up to 10,000 URLs go in one call, at once.

8. Check the result

Open the page, paste two or three URLs of your site and submit. Every URL gets one row per engine:

AnswerMeaning
Google 200Accepted. Google guarantees faster crawling only for pages with JobPosting or BroadcastEvent structured data; other pages are accepted with no promise.
Google 403The service account is not Owner of that property: back to step 4, and check the property matches the URL form (www, https).
Google 429Quota used up: the URL is queued and the cron sends it tomorrow.
IndexNow 200 / 202Accepted (202: the key is still being checked).
IndexNow 403The key file is missing or different: open https://your-site.com/<key>.txt.
offThat engine is not configured: its variable is missing or not visible to PHP.

9. Job boards: sitemap, expired jobs, WordPress, API

Whole sitemap. Paste the sitemap address in the page (sitemap index and .xml.gz work), or let cron send it every night; add --deleted for a sitemap of removed jobs:

30 2 * * *  php /path/to/index.php --sitemap https://your-board.com/job-sitemap.xml
45 2 * * *  php /path/to/index.php --urls /path/to/expired-urls.txt --deleted

Expired jobs. Google for Jobs keeps showing a posting until it is told the page is gone. Choose "Removed" in the page, or send the URL with type deleted: it goes out as URL_DELETED.

WordPress (WP Job Manager, WP Job Openings). Download the free bridge, save it as wp-content/mu-plugins/google-indexer-wp.php and add two lines to wp-config.php:

define('GI_ENDPOINT', 'https://your-board.com/indexer/index.php');
define('GI_API_TOKEN', 'the same token as GI_API_TOKEN above');

A published job is sent as URL_UPDATED; an expired, unpublished or deleted job as URL_DELETED. Publishing never waits for Google.

Any other CMS. One HTTP call when a job goes live or comes down:

curl -X POST 'https://your-board.com/indexer/index.php?api=1' \
  -H 'X-GI-Token: a-long-random-token' -H 'Content-Type: application/json' \
  -d '{"urls":["https://your-board.com/jobs/123"],"type":"updated"}'

The answer is JSON: {"ok":true,"results":[{"url":"…","status":"accepted"}],"queued":0}; "type":"deleted" removes the URL. A wrong or missing token gets 401.

Done

From now on: paste the new URLs, submit, read each engine's answer; the log keeps every submission.

Google Instant Indexer — $24.99 one-time

Stuck on a step? Write to deus@lumnika.com with the answer the page shows for that URL.

Terms · Privacy · Refunds (7-day money-back) · Contact