GoAccess - open source real-time web log analyzer (analytics, privacy)

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.

Terminal window
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.

.config/systemd/user/goaccess.service
[Unit]
Description=GoAccess
After=network-online.target
[Service]
Type=oneshot
RemainAfterExit=false
ExecStart=/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.log
TimeoutStopSec=30
[Install]
WantedBy=default.target

Activate, enable, and run.

Terminal window
systemctl --user daemon-reload
systemctl --user enable goaccess.service
systemctl --user start goaccess.service

Let’s create a timer.

~/.config/systemd/user/goaccess.timer
[Unit]
Description=GoAccess
[Timer]
OnCalendar=*:0/15
Persistent=true
Unit=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.

Terminal window
systemctl --user daemon-reload
systemctl --user enable --now goaccess.timer
systemctl --user start goaccess.timer
systemctl --user status goaccess.timer
systemd-analyze verify ~/.config/systemd/user/goaccess.timer
journalctl --user -u goaccess.timer

You can also list all timers.

Terminal window
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:

Terminal window
mkdir -p containers/geolite2

Create service.

~/.config/systemd/user/geolite2.service
[Unit]
Description=GeoLite2
After=network-online.target
[Service]
Type=oneshot
RemainAfterExit=false
ExecStart=/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/geoipupdate
TimeoutStopSec=30
[Install]
WantedBy=default.target

Reload daemon, enable and run service.

Terminal window
systemctl --user daemon-reload
systemctl --user enable geolite2.service
systemctl --user start geolite2.service

The created directory should now contain three files:

Terminal window
ls containers/geolite2/
GeoLite2-ASN.mmdb GeoLite2-City.mmdb GeoLite2-Country.mmdb

Create a timer that updates the DB every week.

~/.config/systemd/user/geolite2.timer
[Unit]
Description=GeoLite2
[Timer]
OnCalendar=weekly
Persistent=true
Unit=geolite2.service
[Install]
WantedBy=timers.target

Enable the timer.

Terminal window
systemctl --user daemon-reload
systemctl --user enable --now geolite2.timer
systemctl --user start geolite2.timer

Finally, let’s modify the GoAccess container configuration to utilize the GeoLite2 database so that geolocation data can be displayed.

.config/systemd/user/goaccess.service
[Unit]
Description=GoAccess
After=network-online.target
[Service]
Type=oneshot
RemainAfterExit=false
ExecStart=/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.log
TimeoutStopSec=30
[Install]
WantedBy=default.target

Reload and restart the service.

Terminal window
systemctl --user daemon-reload
systemctl --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.