Skip to main content

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:

ModeIt needsSymptom when missing
queueA worker serving the matomo queueJobs pile up in the queue, nothing reaches Matomo
batchYour scheduler running, or matomo:workThe buffer grows and never drains
batch + database driverThe migration to have runErrors about a missing table
syncNothingIf 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 to bots.allow, which overrides every other detection layer. Do not switch detect_generic off for one false positive.
  • route — the path matches tracking.except_routes. The shipped defaults include livewire/*, health* and the usual dashboards.
  • environmenttracking.environments does not include the current environment.
  • dnt — the visitor sends DNT: 1 or Sec-GPC: 1. This one you should leave alone.
  • ability — the logged-in user passes an ability in tracking.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.auto is true and you also attached matomo.track to routes explicitly.
  • spa.enabled is 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.