Statistiky návštěvnosti bez Google Analytics: GoAccess z logů Apache za Cloudflare

Návštěvnost svých pěti webů sleduji bez Google Analytics, bez cookies a bez externí služby. Statistiky počítá GoAccess přímo z logů Apache na mém domácím serveru, každou hodinu je aktualizuje a zobrazuje jen v domácí síti. Aby čísla dávala smysl, musel jsem nejdřív naučit Apache vidět skutečné adresy návštěvníků za Cloudflare.

Proč ne Google Analytics

Weby běží na vlastním serveru s Proxmoxem (viz Migrace pěti webů z VPS na vlastní server) a chtěl jsem, aby i statistiky zůstaly u mě. Zvažoval jsem dvě cesty:

 Rozbor logů (GoAccess)Měřicí skript (Matomo)
Zásah do webůžádnýskript nebo modul v každém webu
Cookies a souhlasnic se do prohlížeče neukládálze nastavit bez cookies
Provozjeden balíček, pár MBvlastní kontejner s PHP a databází, dostupný z internetu
Detailnávštěvy, stránky, zdroje, prohlížeče, chyby 404navíc čas na stránce, cesta návštěvníka, cíle

Pro osobní web a pár firemních prezentací stačí rozbor logů. Matomo si nechávám jako možné rozšíření.

Schéma: návštěvník přes Cloudflare na Apache, mod_remoteip zapisuje skutečnou IP do logu, GoAccess každou hodinu vytvoří přehled dostupný jen z domácí sítě

Past: v logu jsou jen adresy Cloudflare

Domácí připojení je za CGNAT, takže weby jsou dostupné přes Cloudflare proxy a IPv6 (Domácí server za CGNAT). Každé spojení na server proto přichází z adresy Cloudflare. Statistika by ukazovala několik „návštěvníků“ s obrovským počtem přístupů.

Cloudflare posílá skutečnou adresu v hlavičce CF-Connecting-IP. Apache ji umí převzít modulem mod_remoteip, ale jen od důvěryhodných adres, aby hlavičku nemohl podvrhnout kdokoli jiný. Server je za Cloudflare dostupný jen přes IPv6, proto stačí IPv6 rozsahy Cloudflare (aktuální seznam je na cloudflare.com/ips):

# /etc/apache2/conf-available/cloudflare-remoteip.conf
RemoteIPHeader CF-Connecting-IP
RemoteIPTrustedProxy 2400:cb00::/32
RemoteIPTrustedProxy 2606:4700::/32
RemoteIPTrustedProxy 2803:f800::/32
RemoteIPTrustedProxy 2405:b500::/32
RemoteIPTrustedProxy 2405:8100::/32
RemoteIPTrustedProxy 2a06:98c0::/29
RemoteIPTrustedProxy 2c0f:f248::/32

# Log s adresou klienta (%a) misto adresy spojeni (%h)
LogFormat "%a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined

Poslední řádek je podstatný a často se na něj zapomíná. Standardní formát combined v Debianu zapisuje %h, tedy adresu spojení, a ta zůstane adresou Cloudflare i se zapnutým modulem. Adresu převzatou z hlavičky obsahuje %a. Předefinování formátu v samostatném souboru platí pro všechny weby najednou, protože se načítá po hlavní konfiguraci.

a2enmod remoteip
a2enconf cloudflare-remoteip
apache2ctl configtest
systemctl reload apache2

Ověřil jsem to návštěvou z mobilu přes mobilní data. V logu se místo adresy Cloudflare objevila adresa operátora. Vedlejší výhoda: fail2ban na serveru teď vidí skutečné adresy a nehrozí, že by omylem zablokoval Cloudflare a s ním všechny návštěvníky.

GoAccess: přehled pro každý web zvlášť

GoAccess je v repozitáři Debianu 13 (verze 1.9.3). Každý web má vlastní log, takže skript vytvoří samostatný přehled pro každou doménu a rozcestník:

apt install -y goaccess
#!/bin/bash
# /usr/local/bin/goaccess-report.sh
OUT=/var/www/goaccess
DB=/var/lib/goaccess
L=/var/log/apache2
OPTS="--log-format=COMBINED --persist --anonymize-ip --ignore-crawlers --exclude-ip=2001:db8:1:2::-2001:db8:1:2:ffff:ffff:ffff:ffff"
mkdir -p "$OUT"
for SITE in lubomir-bena.cz lubomirbena.cz DALSI_DOMENY; do
  mkdir -p "$DB/$SITE"
  if [ "$1" = "prvni" ]; then
    zcat -f $(ls -1r $L/$SITE-access.log.*.gz 2>/dev/null) $L/$SITE-access.log.1 $L/$SITE-access.log \
      | goaccess - $OPTS --db-path="$DB/$SITE" --html-report-title="$SITE" -o "$OUT/$SITE.html" >/dev/null 2>&1
  else
    goaccess $L/$SITE-access.log.1 $L/$SITE-access.log $OPTS --restore --db-path="$DB/$SITE" \
      --html-report-title="$SITE" -o "$OUT/$SITE.html" >/dev/null 2>&1
  fi
done

Na konci skript ještě vypíše jednoduchý index.html s odkazy na jednotlivé přehledy. Důležité volby:

  • --persist a --restore: GoAccess si zpracovaná data ukládá a při dalším běhu přidá jen nové řádky. Apache rotuje logy denně a drží jich jen 7, statistika ale díky tomu sahá dál do minulosti.
  • čtení .log.1 i .log: kolem půlnoci se log rotuje. Bez předchozího souboru by se ztratily poslední minuty dne. Řádky, které už GoAccess zpracoval, přeskočí.
  • --anonymize-ip: v přehledech nejsou celé adresy návštěvníků.
  • --ignore-crawlers: roboti vyhledávačů a AI se nezapočítají.
  • --exclude-ip s rozsahem domácí sítě: moje vlastní návštěvy webů jdou také přes Cloudflare a bez vyřazení by se počítaly.

Parametr prvni jsem spustil jednou. Načetl historii ze všech uchovaných logů, starší soubory postupně od nejstaršího. Data před zapnutím mod_remoteip obsahují místo návštěvníků adresy Cloudflare, takže počty unikátních návštěvníků za tyto dny jsou nepřesné.

Aktualizaci každou hodinu zajišťuje cron:

echo "5 * * * * root /usr/local/bin/goaccess-report.sh" > /etc/cron.d/goaccess

Zobrazení jen v domácí síti

Statistiky nechci mít na internetu. Přehledy proto obsluhuje samostatný virtuální host Apache na portu 8080, který poslouchá jen na domácí IPv4 adrese serveru a pouští jen adresy z domácí sítě:

Listen 192.0.2.10:8080
<VirtualHost 192.0.2.10:8080>
    DocumentRoot /var/www/goaccess
    <Directory /var/www/goaccess>
        Options -Indexes
        Require ip 192.0.2.0/24
    </Directory>
    CustomLog /dev/null combined
</VirtualHost>

Zvenku se k přehledům nikdo nedostane: IPv4 je za CGNAT a firewall MikroTiku pouští na server jen porty 80 a 443 z Cloudflare přes IPv6. CustomLog /dev/null zajistí, že se prohlížení statistik nepočítá do statistik. Přehled od GoAccess je jeden HTML soubor s grafy a nenačítá nic z internetu.

Co v přehledech vidím

Pro každý web vidím unikátní návštěvníky a přístupy po dnech, nejčtenější stránky, odkazující weby, operační systémy a prohlížeče a chyby 404. Hned první den ukázal i to, co z Google Analytics nevyčtu: desítky pokusů o /.env, /.git/config nebo /wp-login.php. Jsou to automatické skenery, které hledají zranitelnosti cizích aplikací. Drupal tyto soubory nemá, takže jsou neškodné. Část skenerů se ale tváří jako běžný prohlížeč, a proto je skutečný počet lidí o něco nižší, než ukazuje řádek „Unique Visitors“.

Vyhledávací dotazy, přes které lidé na web přišli, v logu nejsou. Ty doplňuje Google Search Console, se kterou statistiky z logů dobře doplňují.

Shrnutí

  • Za Cloudflare je potřeba mod_remoteip s důvěryhodnými rozsahy a log s %a místo %h.
  • GoAccess z logů Apache nepotřebuje žádný skript ve webu ani cookies.
  • --persist a --restore drží historii déle, než se uchovávají logy.
  • Vlastní návštěvy a roboty je dobré vyřadit, skenery zranitelností zůstanou částečně započítané.
  • Přehledy jsou dostupné jen z domácí sítě a aktualizují se každou hodinu.

Článek navazuje na sérii Migrace pěti webů z VPS na vlastní server a na článek Webový server v Proxmox LXC.