Troubleshooting
Almost every "it is not working" turns out to be one of a dozen things. Start with the two commands, then find your symptom below.
php artisan matomo:test # is the write side reachable?
php artisan matomo:report VisitsSummary.get # is the read side authorized?
The one diagnostic worth wiring up first
Every time the gate declines a hit, it fires a
VisitorExcluded event carrying the reason. Listening for it turns guesswork into an
answer:
use Illuminate\Support\Facades\Event;
use MatomoAnalytics\Events\VisitorExcluded;
Event::listen(fn (VisitorExcluded $event) => logger()->info("Matomo excluded: {$event->reason}"));
If the log says bot, you have a detection false positive. If it says not_configured,
your environment variables are not reaching the process. If nothing is logged at all, the
hit was never attempted — the problem is upstream of the gate.
No data arrives at all
Check the master switch first. MATOMO_ENABLED ships off, and enabled is the
very first rule the gate consults: with it false, nothing is tracked however complete the
rest of the configuration is, and the only signal is a VisitorExcluded event with the
reason disabled.
Then the connection. Tracking is also a no-op unless both MATOMO_HOST and
MATOMO_SITE_ID are set. That is by design too. A
config:cache built before the variables were added will also keep the old values —
re-run it. matomo:test names the settings this happened to, so you do not have to guess
which ones the application is missing; see
the command reference.
Then check the mode. Each mode has one operational dependency:
| Mode | It needs | Symptom when missing |
|---|---|---|
queue | A worker serving the matomo queue | Jobs pile up in the queue, nothing reaches Matomo |
batch | Your scheduler running, or matomo:work | The buffer grows and never drains |
batch + database driver | The migration to have run | Errors about a missing table |
sync | Nothing | If this fails, the problem is the connection |
matomo:test bypasses all of it and sends directly, which is why it is the right first
step: if it succeeds and your application's hits do not arrive, the problem is delivery,
not configuration.
Some visitors are missing
That is the gate doing its job, and the VisitorExcluded reason tells you which rule.
Common ones:
bot— a legitimate client matched a detection signal. Add its User-Agent token tobots.allow, which overrides every other detection layer. Do not switchdetect_genericoff for one false positive.route— the path matchestracking.except_routes. The shipped defaults includelivewire/*,health*and the usual dashboards.environment—tracking.environmentsdoes not include the current environment.dnt— the visitor sendsDNT: 1orSec-GPC: 1. This one you should leave alone.ability— the logged-in user passes an ability intracking.except_abilities. Easy to hit accidentally with a broad ability name.
Every hit shows my server's IP, or the wrong time
No MATOMO_TOKEN. Matomo only honors the forwarded client IP and the hit timestamp when a
token accompanies the hit; without one it uses the connection's IP and the moment of
receipt. In queue or batch mode that receipt time can be far from the event time. See
installation.
If a token is set and the IP is still wrong, you are behind a proxy: read the
ip_header warning.
The client-side snippet renders nothing
@matomoScript renders an empty string when the master switch is off, js.enabled is
false, or the instance is not configured. View the page source: if there is no
dns-prefetch link either, it is one of those three rather than a JavaScript problem.
SPA navigations are not tracked
In order of likelihood: spa.enabled is false (it is off by default); the enabled
adapters do not include the one your application uses; or you load a
Tag Manager container, in
which case the spa block does nothing and Tag Manager's own history trigger is what you
need.
To check the adapter, watch for the framework's navigation event in the browser console. If it fires and no page view follows, the adapter is not enabled; if it never fires, the adapter is the wrong one.
Page views are doubled
Two causes, both configuration rather than a bug:
middleware.autois true and you also attachedmatomo.trackto routes explicitly.spa.enabledis on in an application that does full page loads, so the hard load and the adapter both fire.
Web Vitals produce nothing
A 404 from the ingest route means web_vitals.enabled is false — the route is always
registered and the controller refuses when the feature is off. A 422 means the beacon
was rejected: the metric is not in web_vitals.metrics, or the value was not numeric.
Silence with no request at all means Google's web-vitals library is not on
window.webVitals. The snippet is a deliberate no-op in that case; bundle the library or
set web_vitals.library.
A 500 carrying MissingRateLimiterException is a version rather than a configuration.
Releases 0.27.0 through 0.28.2 registered the endpoint's named rate limiter inside the
package's routes file, and php artisan route:cache never executes that file — so a deploy
that caches its routes kept the compiled throttle: middleware while the limiter it names
was never registered. Every beacon into such an installation failed and its measurement was
discarded. Upgrade: the registration happens when the service provider boots, which a cached
route table does not skip. If you cannot upgrade yet, register the limiter yourself from
AppServiceProvider::boot() under the name
MatomoAnalyticsServiceProvider::WEB_VITALS_LIMITER; setting web_vitals.throttle to null
and rebuilding the route cache also clears the error, but it drops the middleware along with
the limiter and leaves a public endpoint unbounded.
Reporting returns null
MatomoReports::lastError() carries Matomo's own message. Two causes dominate: the token
lacks view access to the site, and the query timed out — reporting.timeout is
separate from the tracking timeout and a long date range legitimately needs more.
A GDPR call failing the same way usually means the token lacks admin access, which is a higher bar than reporting needs.
matomo:annotate --release does nothing
It is a no-op unless annotations.release is true, and it says so when it skips. If it
runs but the note is just Deployed with no version, no version resolved — see
release annotations.
The dead-letter queue keeps growing
php artisan matomo:replay --list
The listing shows the error and the attempt count per entry, which distinguishes a misconfiguration (every entry failing the same way) from a payload problem (one entry failing differently). Fix the cause before replaying, or the entries come straight back.
A batch retries forever and is never dead-lettered
The consecutive-failure counter lives in the cache. With the array cache store it resets
every process, so batch.max_attempts is never reached. Use a persistent cache store in
any environment running batch mode — see reliability.
config:cache fails
A closure in config/matomo-analytics.php cannot be serialized. Both extension points
that accept a callable — tracking.gate and bots.detector — also accept an invokable
class-string, which is the form to use. See
configuration.
Still stuck
Open an issue on
the public repository
with the output of matomo:test, your mode, and the VisitorExcluded reasons you see.
Security issues go to the
security policy
instead, never a public issue.