I’ve always wanted a robust web analytics system for my blog, one that respects privacy by avoiding cookies, JavaScript, and other tracking methods. Recently, I discovered a tool called GoAccess that fits the bill perfectly.
GoAccess is an open source real-time web log analyzer and interactive viewer that runs in a terminal in *nix systems or through your browser. It provides fast and valuable HTTP statistics for system administrators that require a visual server report on the fly.
GoAccess parses log files from sources like Apache, Nginx, Amazon S3, Elastic Load Balancing, CloudFront, Caddy, and others to generate insightful web analytics. It can run in a terminal (using ncurses) or produce output in HTML, JSON, or CSV formats.
Since I don’t require real-time analytics, the plan is to schedule a job that runs every 15 minutes, generates an HTML file, and stores it in a directory that serves static files for my blog. This way, the analytics page is accessible from /analytics
.
Although I could install GoAccess using dnf
, I opted to use a Podman container for improved security. After all, running tasks inside a container is generally more secure than running them directly on the system.
I’m assuming you already have a Caddy server set up on Fedora, as described in this guide.
Test the container.
podman run --rm -v ~/containers/caddy/logs:/logs:ro -v ~/containers/caddy/sites/example.com:/output:rw docker.io/allinurl/goaccess:1.9.3 --ignore-crawlers -a -o /output/analytics.html --log-format=CADDY /logs/example_com_access.log
We could have used cron to schedule the job, but this time I prefer to use systemd
. Since I couldn’t find a way to run a container as a timer, I decided to use a service instead.
[Unit]Description=GoAccessAfter=network-online.target
[Service]Type=oneshotRemainAfterExit=falseExecStart=/usr/bin/podman run --rm -v /home/<user>/containers/caddy/logs:/logs:ro -v /home/<user>/containers/caddy/sites/example.com:/output:rw docker.io/allinurl/goaccess:1.9.3 --ignore-crawlers -a -o /output/analytics.html --log-format=CADDY /logs/example_com_access.logTimeoutStopSec=30
[Install]WantedBy=default.target
Activate, enable, and run.
systemctl --user daemon-reloadsystemctl --user enable goaccess.servicesystemctl --user start goaccess.service
Let’s create a timer.
[Unit]Description=GoAccess
[Timer]OnCalendar=*:0/15Persistent=trueUnit=goaccess.service
[Install]WantedBy=timers.target
=*:0/15
means the service will run at 00, 15, 30, and 45 minutes past each hour, while Persistent=true
ensures that if the system is off at the scheduled time, the missed run is executed as soon as possible after boot.
Activate, enable it, and check the status.
systemctl --user daemon-reloadsystemctl --user enable --now goaccess.timersystemctl --user start goaccess.timersystemctl --user status goaccess.timersystemd-analyze verify ~/.config/systemd/user/goaccess.timerjournalctl --user -u goaccess.timer
You can also list all timers.
systemctl --user list-timers
There’s one more feature I’d like to add: displaying users’ geolocation. For this, I’m using geoipupdate to download the GeoLite2 binary database via Podman and systemd timers, scheduled to run once a week.
To use GeoLite, you need to create a free account to obtain an account ID and a license key. You can read more about it here: https://dev.maxmind.com/geoip/geolite2-free-geolocation-data/.
Before running, create a directory for the GeoIP database:
mkdir -p containers/geolite2
Create service.
[Unit]Description=GeoLite2After=network-online.target
[Service]Type=oneshotRemainAfterExit=falseExecStart=/usr/bin/podman run --rm -e GEOIPUPDATE_ACCOUNT_ID=XXXXXX -e GEOIPUPDATE_LICENSE_KEY=XXXXXXXXXXXXXXXX -e 'GEOIPUPDATE_EDITION_IDS=GeoLite2-ASN GeoLite2-City GeoLite2-Country' -v /home/<user>>/containers/geolite2:/usr/share/GeoIP:rw ghcr.io/maxmind/geoipupdateTimeoutStopSec=30
[Install]WantedBy=default.target
Reload daemon, enable and run service.
systemctl --user daemon-reloadsystemctl --user enable geolite2.servicesystemctl --user start geolite2.service
The created directory should now contain three files:
ls containers/geolite2/GeoLite2-ASN.mmdb GeoLite2-City.mmdb GeoLite2-Country.mmdb
Create a timer that updates the DB every week.
[Unit]Description=GeoLite2
[Timer]OnCalendar=weeklyPersistent=trueUnit=geolite2.service
[Install]WantedBy=timers.target
Enable the timer.
systemctl --user daemon-reloadsystemctl --user enable --now geolite2.timersystemctl --user start geolite2.timer
Finally, let’s modify the GoAccess container configuration to utilize the GeoLite2 database so that geolocation data can be displayed.
[Unit]Description=GoAccessAfter=network-online.target
[Service]Type=oneshotRemainAfterExit=falseExecStart=/usr/bin/podman run --rm -v /home/<user>/containers/caddy/logs:/logs:ro -v /home/<user>/containers/caddy/sites/example.com:/output:rw -v /home/<user>/containers/geolite2:/geolite2:ro docker.io/allinurl/goaccess:1.9.3 --geoip-database /geolite2/GeoLite2-Country.mmdb --ignore-crawlers -a -o /output/analytics.html --log-format=CADDY /logs/example_com_access.logTimeoutStopSec=30
[Install]WantedBy=default.target
Reload and restart the service.
systemctl --user daemon-reloadsystemctl --user restart goaccess.service
After reloading and restarting the GoAccess service, we now have a complete web analytics solution that operates without a database, cookies, or additional JavaScript files, providing a privacy-friendly alternative for your site.
You can preview it here.