Troubleshooting
Русская версия
Start with php artisan indexnow:check, then php artisan indexnow:explain "App\Models\Post" <id>, then the log
channel at debug.
Nothing is sent
| Symptom |
Cause |
Fix |
check: configuration: ... and exit 1 |
an env() value is invalid; IndexNow runs disabled |
fix the value; the exact error is printed |
explain: when: published -> false right after create() |
the when attribute only has a database default, so the fresh model does not have it |
protected $attributes = ['published' => false] on the model, or set it explicitly |
explain: no #[IndexNow] rule |
the model has no attribute and was not registered |
add the attribute, or IndexNowKit::observe() |
| URLs resolved but no POST |
dispatch: queue and no worker |
php artisan queue:work, or dispatch: sync |
log: rule "..." ignores this update (fields ...) |
fields filter did not match the changed attributes |
add the attribute to fields, or drop the filter |
log: Cannot generate route "posts.show": Missing required parameter |
the rule's params do not match the route |
params: ['post' => 'self'] for route model binding, or name every parameter |
log: Cannot read "foo" on App\Models\Post: no method foo(), getFoo(), isFoo() or hasFoo(), no property "foo"… |
typo in an accessor |
fix the accessor; attributes, casts, accessors, relations and methods are all valid |
a mass update() changed nothing in the index |
bulk statements fire no events (A13) |
IndexNowKit::submitModels($query->get()) or indexnow:submit-model |
attach() on a pivot changed nothing |
pivot operations fire no owner events |
$touches = ['posts'] on the related model, rule without fields filter |
Sent, but the engine answers
| Answer |
Meaning |
Fix |
403 (invalid_key, job failed) |
https://<host>/<key>.txt is not reachable or has another body |
indexnow:check; a CDN may cache the old file (key_file.cache_max_age) |
422 (unprocessable) |
URLs of another host than host, or key file on another host |
one key per host (hosts), strict_hosts: true |
429 (rate_limited) |
too many requests |
the job releases with Retry-After; lower throttle.max_requests_per_minute |
202 (pending) |
accepted, key verification pending |
normal for a new key; check --live later answers 200 |
Duplicates, timing
- The same URL is not resubmitted within
debounce.per_url (600 s). --force bypasses it; debounce.store: cache
shares the window between requests and workers, memory does not.
- Everything from one request leaves as one batch on
terminating; a job saving models flushes after the job.
- A rolled-back transaction submits nothing; a savepoint rollback inside
DB::transaction() drops only the inner
URLs.
Staging submitted its URLs
| Symptom |
Cause |
Fix |
Bing/Yandex report URLs of staging.example.com, or failed / unprocessable (422) for them in the log |
the staging copy runs with the production key and no dry_run; its URLs were generated on its own host |
outside production set INDEXNOW_DRY_RUN=1 (or INDEXNOW_ENABLED=0); check fails on such a copy since core 0.6 |
| the staging host serves the production key file |
key_file.enabled is on everywhere |
key_file.enabled: false outside production, so no engine can verify the key on that host |
| the engines indexed staging pages |
the staging host answered 200 for them and served the key |
return 410 (or noindex + block in robots.txt) on staging, and rotate the key if it was exposed |
| a preview environment must submit on purpose |
— |
say dry_run: false explicitly in that environment; check then warns instead of failing |
Duplicates with memory and several workers
| Symptom |
Cause |
Fix |
| the same URL is submitted by every worker within minutes |
debounce.store: memory is per process; each web worker and queue worker keeps its own window |
debounce.store = a shared cache; check warns about memory |
| duplicates right after a cache outage |
the store fails open: no deduplication while the cache is down |
expected and bounded (one request per URL); watch the debounce store unavailable warning rate |
| duplicates after a deploy |
the shared cache was flushed, or debounce.key_prefix changed |
harmless once; keep the prefix stable per application |
Testing environments
Outside production_environments a missing key enables dry_run: requests are logged, not sent. check warns
about it; in a production environment dry_run is an error.
Where things are logged
Channel indexnow.logging.channel (default channel otherwise). Levels: success debug, 202 info, 403/400
error, 422/429/5xx warning, invalid configuration critical, silent decisions (when false, fields mismatch)
debug. The full list is in the
core operations guide.