Half your traffic is not a person.
Loghound proves which half.

An open-source traffic analyser and bot detector for the access logs you already have. It catches headless Chrome on rotating residential proxies — the traffic every analytics product on the market counts as human — and it measures how long somebody actually looked at your page, not how long a tab existed.

Free and open source, MIT licensed. No account, no cloud, no telemetry. It runs on your server, reads your logs read-only, and stores everything in a Solr you own — or one we host for you.

Reason one

It detects the bots everything else counts as human

Not a User-Agent blocklist. Dozens of addresses on unrelated consumer ISPs, sharing one byte-identical header fingerprint, each fetching a single page. From inside any one plane of observation that is indistinguishable from a crowd of real visitors. Loghound watches three planes and cross-checks them.

Reason two

It measures real time on site

Conventional analytics pings while the page is merely loaded, so a tab left open in a background window reports as an hour of engagement. Loghound keeps three separate clocks — wall, visible, engaged — and never conflates them.

3independent detection planes, cross-checked against each other
17weighted rules, every one of them explained on the verdict
4timing numbers kept apart instead of averaged into a lie
0dependencies — no Composer, no npm, no build step
01 The problem, stated concretely

The traffic looks human from every angle you are currently looking from

Loghound was not written from a threat model. It was written because a capture of this site's own traffic did not survive being looked at properly.

Roughly half of the sessions a conventional analytics tool reported on opensolr.com carried a hard bot signature. Identical header fingerprints across unrelated consumer ISPs. One page each, never two. Every one of them reported at exactly ten seconds on site.

They were not caught by anything, and it is worth being precise about why. This traffic does everything right:

What it does that defeats the usual checks

Every one of these is why the ordinary defences report nothing.

  • It fetches sub-resources. Images, CSS, JavaScript, the favicon. "No assets" never fires.
  • It executes JavaScript. It reaches the site's own analytics endpoint with a full payload — screen resolution, language, timezone. That endpoint is only ever reached by code that ran in a JavaScript engine, so the site's analytics counted every one of them as a human visitor.
  • The User-Agents are ordinary. Recent Chrome on Windows 10 and on Linux. Nothing in any blocklist.
  • The addresses are spread across unrelated networks, which defeats every per-address rate limit.
  • One of them even downloads a 10 MB file — engagement, by any conventional measure.

What it cannot hide

The whole value of a rotating proxy fleet is that it is one automation stack wearing many addresses.

  • The header tuple is byte-identical across addresses on completely different ISPs. It has to be: the stack behind them is one program.
  • Three of them began a session in the same second. Then two more did it again seven minutes later. Five independent humans do not do that.
  • Every one fetched exactly one page and left. The session shape is identical across all of them.
  • Nobody scrolled, clicked or moved a pointer. Ten seconds of "time on site" with no interaction of any kind in it.
The capture is in the repository. tests/fixtures/apache_combined_bot_fleet.log holds 75 real requests from 13 addresses, each in a different /24, anonymised only where a real client address needed to be. The test suite classifies them, and you can re-run it yourself in about a second with no network access. Nothing about this page is a hypothetical.
02 The signal that catches it

Hash everything except the address

A fleet on rotating residential proxies changes its exit address on every request and changes nothing else. So any fingerprint that includes the address identifies nothing, and one that deliberately excludes it collapses the fleet back into a single row. The rotation stops being camouflage and becomes the signature.

FIVE ADDRESSES · FIVE UNRELATED NETWORKS · ONE PAGE EACH192.177.172.11706:50:4382.22.90.19406:50:43104.164.218.24406:50:4345.38.234.21406:57:4731.132.55.5806:57:47ONE HEADER FINGERPRINTUser-Agent, Accept, Accept-Language, Accept-Encoding,the client hints, the Sec-Fetch headers, the protocol.The IP address is left out on purpose.5 distinct addresses → verdict: bot, class proxy_fleet

The fingerprint-cluster view, in outline: one row per header fingerprint, with the count of distinct addresses that shared it, expanding to the member addresses and their networks. Five or more on non-mobile networks within a day is worth 80 of the 100 points needed for a bot verdict — enough on its own. This is a diagram of the view, not a screenshot.

Why the mobile exclusion exists. Carrier-grade NAT genuinely puts thousands of real people behind a handful of addresses, so the cluster rule skips networks classified as mobile. That is also the rule's weak point, stated plainly: an ASN misclassification turns into a batch of false positives, and if a whole organisation shows up as a fleet you should check the network type before you believe it.
03 How it works

Three independent planes, cross-checked

Each plane alone is defeatable, and essentially every existing tool uses exactly one of them. All the power is in correlating the three — because the things that make a scraper survive the JavaScript plane are the things that give it away on the behavioural one.

1 · TRANSPORTheaders, network, reverse DNS2 · BEHAVIOURsession shape, timing, clusters3 · EXECUTIONwhat is running the JavaScriptCORRELATEA scraper now has todefeat all three at once,and they pull apart.ONE VERDICTbot · likely_botunknown · likely_humanhuman — with its reasons

Log-only analysers see plane 1. JavaScript-only analytics sees plane 3, and trusts it: a client that runs the script is a human, as far as it is concerned. That single assumption is why headless Chrome on residential proxies is counted as human traffic almost everywhere.

It reads the logs you already have

Apache, nginx and Caddy, including JSON formats. The setup wizard reads your web server configuration for the log format and file list, then shows you the mapping with five of your own log lines rendered as parsed records and a confidence percentage. Nothing is ever ingested with a silently guessed format.

It stores into Solr

One document per log line, one per session. The fingerprint-cluster analysis is a JSON Facet query, which is why it stays fast on a busy site instead of turning into a table scan. Run your own Solr, or let Opensolr host the index.

An optional JavaScript beacon

One line on your site. It reports whether JavaScript ran at all, whether the engine actually matches the version the User-Agent claims — a spoofed User-Agent cannot retrofit a JavaScript engine — and whether a human plausibly drove the pointer. No cookies, nothing stored on the visitor's device.

Every verdict carries its reasons

A score with no explanation is not evidence, so a verdict without reasons is treated as a bug rather than a rounding error. Seventeen weighted rules, each with a code, and the ruleset version is written onto every session so a historical verdict stays traceable to the rules that produced it.

Honest crawlers are kept separate

Googlebot and GPTBot are bots, and counting them as human traffic is how dashboards start lying to you — but they are presented apart from evasive traffic, always. Conflating "Googlebot indexed 400 pages" with "someone is scraping you from 200 residential addresses" is exactly what makes the usual tools useless here.

Your log files are never touched

Opened read-only, and never written to, truncated, rotated, renamed or deleted. The tailer survives logrotate, copytruncate, deletion while the file is held open, and inode recycling — the case where a rotation hands the new file the number the old one just freed and a naive tailer quietly stops ingesting while looking perfectly healthy.

04 Real time on site

Four numbers, kept apart

A visitor opens your article in a background tab at nine, gets distracted, comes back twenty minutes later, reads for four minutes with regular scrolling, then switches window and leaves the tab open until ten. Here is what each tool would tell you about that visit.

ONE VISIT · OPENED 09:00 · READ 09:20–09:24 · TAB LEFT OPEN UNTIL 10:00log spanabout 0 — what a log-only analyser can seewall60 minutes — what conventional analytics reportsvisible4 minutes — visible on screen and focusedengaged4 minutes — somebody was actually there

Bars to scale. The industry number is fifteen times the true one on this visit, and that is not an edge case — it is what a browser with tabs does all day.

How the clocks work

Three of them come from the beacon; the fourth comes from the log alone.

  • Wall runs while the page exists. Nothing else.
  • Visible runs only while the tab is on screen and the window has focus.
  • Engaged runs only while visible and within thirty seconds of a real scroll, click, keypress or pointer movement.
  • Log span is last request minus first request — the only number a log-only tool has, and it is structurally blind to the last page of every visit.

Why the numbers are trustworthy

Small decisions, but they are the difference between a measurement and a guess.

  • It sees the last page of a session. The beacon flushes on pagehide, so the final pageview is measured — something a log-based tool structurally cannot do, because there is no later request to measure against.
  • Monotonic clocks only. A clock correction or a daylight-saving change can never be added to somebody's reading time.
  • A page that starts hidden accrues nothing. Prerendered and background-tab pages start at zero and stay there until they are actually shown.
  • Missing stays missing. When no beacon arrived, the timing fields are absent rather than zero — a zero would enter every average as a real "0 seconds engaged" observation and quietly destroy the metric.
05 How it compares

Written to be fair

Each of these is good at what it set out to do, and for most people one of them is the right answer. Loghound is narrow on purpose, and the last row of this table is the one worth reading.

  Loghound GoAccess Matomo Plausible GA4
Data source logs and a JS beacon logs only JavaScript, with a log importer JavaScript JavaScript
Bot detection three-plane correlation, 17 weighted rules, every verdict carries its reasons User-Agent blocklist User-Agent and address lists, plus some heuristics User-Agent blocklist undisclosed, not inspectable
Catches headless Chrome on residential proxies yes — that is the point no no no no
Cross-address fingerprint clustering yes — the primary signal no no no no
Time on site four distinct numbers, including engaged time and the last page request span only tab-open time tab-open time tab-open time
Storage Solr 9, yours or hosted in-memory or an on-disk report MySQL or MariaDB ClickHouse Google
Install clone and run one script; no Composer, no npm one binary from your package manager a PHP application plus a database, or their cloud Docker and Postgres, or their cloud nothing to install
Cost free, MIT free, MIT free self-hosted, paid cloud paid cloud, free self-hosted free
Where it is genuinely better than Loghound Faster, zero moving parts, a beautiful terminal interface, and it will parse a huge log file in seconds with nothing installed. If your question is "what happened in this log file, now", use GoAccess. A complete analytics product: goals, funnels, ecommerce, heatmaps, A/B testing, session recording, a large plugin ecosystem, mature privacy tooling and a real company behind it. The nicest interface in analytics, a tiny script, no cookies at all, genuinely simple. Excellent for a content site that wants honest pageviews without a project. Free at any scale, integrates with the rest of Google's stack, and the attribution modelling is real work nobody else has replicated.
Loghound is not a replacement for any of them. If your question is "how many people read my blog post", use Plausible. If it is "which campaign drove revenue", use GA4 or Matomo. If it is "how much of this traffic is actually real, and can you prove it", that is the one Loghound was built to answer — and it runs happily alongside whatever else you already have, because it reads logs your web server is already writing and its beacon is independent of every other script on the page.
06 What this does not catch

A bot detector that oversells itself is worse than none

Because you would stop looking. So here is the honest list, and it is the same list that opens the project's own README.

A well-funded, careful scraper

Give every session a genuinely unique and mutually consistent header fingerprint, run a real non-headless browser, synthesise human-like pointer movement and pacing, never reuse an exit address — and Loghound will classify it as human. Everything here raises the cost of scraping. None of it makes scraping impossible, and anyone claiming otherwise is selling something.

Privacy-conscious humans get flagged

The single biggest source of false positives, and it deserves to be first. A visitor running a script blocker, strict tracking protection or a locked-down browser mode may never load the beacon, and from the log alone that is indistinguishable from a non-JavaScript client. The rule for it is deliberately weighted below the threshold so it can never condemn anyone on its own.

Traffic that never reaches your origin

A request served from a CDN edge or a cache in front of you produces no origin log line and does not exist as far as Loghound is concerned. If you run a CDN, you are analysing your cache misses — and Loghound will not silently pretend otherwise.

Low-and-slow scrapers

One page, one address, one fingerprint, once a day, from a residential network. It sits below every threshold in the ruleset by construction. The cluster signal — the strongest thing here — needs a fleet to detect a fleet.

API and native-app traffic

There is no beacon in a native application, so the execution plane is permanently blind there and every client looks like a non-JavaScript one. Point Loghound at that vhost's log and read the transport plane instead.

Carrier-grade NAT

Thousands of real mobile users share a small pool of addresses, which is why the fleet rule explicitly excludes mobile networks — and why an ASN misclassification turns into a batch of false positives rather than one.

It is not a firewall and it blocks nothing

Loghound observes and reports. It issues no challenges, rate-limits nothing and writes no firewall rules. Deciding what to do about what it finds is your job, and that is precisely why every verdict ships with its reasons attached.

It does not do product analytics

No funnels, no goals, no A/B tests, no revenue attribution, no cohort retention. If you need those, one of the tools in the table above is the right answer — and Loghound runs alongside it.

07 Installing it

Clone it, and look before you leap

PHP 8.1 or newer with five standard extensions, Solr 9, systemd, and read access to your access logs. That is the whole dependency list — there is no Composer, no npm and no build step, on purpose, so git clone plus one script works on a bare box.

# Nothing is changed until you drop --dry-run. It prints every command it would # run and every file it would write, so you can read it first. git clone https://github.com/phpcip/loghound.git cd loghound sudo ./install/install.sh --dry-run sudo ./install/install.sh # Start ingesting, and check that documents are actually arriving. sudo systemctl enable --now loghound-tail.service loghound-tail --status --human
<!-- And one line on the site you are measuring, for the execution plane. --> <script src="https://loghound.example.com/b.js?v=1" defer></script>

It will not break your box

It never modifies an existing vhost, PHP-FPM pool, cron entry or service, never overwrites a file it did not write, validates every configuration it generates before enabling it, rolls back if validation fails, and reloads rather than restarts so other sites are undisturbed. It also refuses to deploy a TLS certificate that does not chain to a trusted root.

It works on plain combined

Zero configuration on a default Apache or nginx. Logging a handful of extra headers makes the fingerprint substantially sharper — the documentation gives you the block to paste and a table saying exactly which detection signals each individual header buys, so you can take some of it and not the rest.

Bring your own Solr, or use ours

Point it at any Solr 9 you run and it pushes the two configsets itself. If you would rather not operate one, Opensolr can host the index — setup asks for an email address and an API key, creates both indexes, pushes the configsets and verifies them, and you never have to learn what a configset is.

Your logs are read, never written. Every source file is opened read-only, and Loghound never writes to, truncates, rotates, renames or deletes one — there is a test in the suite that asserts a source file's size is unchanged after a full read. Whatever else is already reading those files carries on undisturbed, and so does logrotate.

Find out what your traffic actually is

Loghound is free, MIT licensed and self-hosted. There is no account to create, nothing phones home, and the whole thing is one git clone away. If you would rather not run a Solr for it, we will host the index.