About me: My name is Solène Rapenne, pronouns she/her. I like learning and sharing knowledge. Hobbies: '(Reproducibility BSD OpenBSD Lisp cmdline gaming security QubesOS internet-stuff). I love percent and lambda characters. Former Qubes OS core team member / OpenBSD developer solene@.

Contact me: solene at dataswamp dot org or @solene@bsd.network (mastodon).

I'm a freelance OpenBSD, FreeBSD, Linux and Qubes OS consultant, this includes DevOps, DevSecOps, technical writing or documentation work.

Web statistics from the server logs with GoAccess

Written by Solène, on 04 November 2026.
Tags: #selfhosting #privacy

Comments on Fediverse/Mastodon

1. Introduction §

I wanted to know if anyone was reading my static websites, without adding a JavaScript tracker to the pages (this does not cover le blog). The web server already writes an access log for each request, so the information is already there, it just needed to be usable for me.

GoAccess is a web log analyzer that reads the access logs of common web servers and produces a report, in the terminal with a TUI or as a single HTML page.

GoAccess official website

In this blog post, you will learn how to generate a GoAccess HTML report, and keep the statistics accumulated over time.

2. Why I like it §

The HTML report is a single page that looks nice and easy to read. It shows the visitors per day, the most requested pages, the 404 errors, the referrers, the operating systems and browsers, the status codes and other data I need to figure.

Having the list of referrer is interesting to know from where the visitors came to the website.

GoAccess uses very little CPU, because it only reads the new lines of the logs since previous instead of parsing the entire logs everytime.

3. Setup §

3.1. Web server logs §

GoAccess needs an access log in a file. I use Caddy, its logs are in JSON line (a full JSON per line) and GoAccess already knows this format. Here is the log directive for a site in the Caddyfile, the rotated files are kept 14 days and are not compressed, so GoAccess can still read them after a rotation:

example.org {
	log {
		output file /var/log/caddy/example.log {
			roll_compression none
			roll_keep_for 14d
		}
	}
	root * /var/www/example
	file_server
}

3.2. The report script §

GoAccess is a single Go binary, packaged in most distributions, so it is easy to deploy.

Create the file /usr/local/bin/goaccess-report.sh with this content and make it executable:

#!/bin/sh

DATA=/var/lib/goaccess/example
mkdir -p "$DATA"

# nothing to restore on the first run
FLAGS=""
test -n "$(ls -A "$DATA")" && FLAGS="--restore"

# read the current log and the rotated ones, oldest first
goaccess $(ls -1tr /var/log/caddy/example*.log) \
	--log-format=CADDY \
	--persist $FLAGS \
	--db-path="$DATA/" \
	--anonymize-ip \
	--ignore-crawlers \
	--exclude-ip=192.168.1.0-192.168.1.255 \
	--no-progress \
	--html-report-title="example.org" \
	-o /var/www/stats/example.tmp.html

# replace the report only once it is complete
mv /var/www/stats/example.tmp.html /var/www/stats/example.html

The interesting parts are --persist and --restore. GoAccess saves its data in the directory given by --db-path, and at the next run it reloads it and remembers where it stopped in each log file, so the statistics keep growing even after the old logs are deleted. GoAccess is using inode number of each log file, so even after a rotation it can figure the checkpoint of previously parsed files.

The flag --anonymize-ip hides the last part of the IP addresses in the report, --ignore-crawlers removes the bots, and --exclude-ip removes my own visits, I exclude my home public IP and my VPN network. You can repeat --exclude-ip as many times as needed, range is supported but not subnets.

3.3. The systemd timer §

In /etc/systemd/system/, create the file goaccess-report.service:

[Service]
Type=oneshot
ExecStart=/usr/local/bin/goaccess-report.sh

And the file goaccess-report.timer to run it every hour:

[Timer]
OnBootSec=5min
OnUnitActiveSec=1h

[Install]
WantedBy=timers.target

Enable the timer:

systemctl daemon-reload
systemctl enable --now goaccess-report.timer

The directory /var/www/stats/ can now be served by any web server. I only publish it on my VPN, because the report is for me and not for the visitors, but there are little risk exposing it on a public website.

4. Conclusion §

GoAccess is not a full analytics solution, it only knows what is in the logs. But for a few static websites, it is enough for me, and it does not add anything to the pages for the visitors.

This does not beat awstats for long term statistics in my opinion, but it is way simpler to use, I had too much troubles with keeping awstats working I prefer to avoid it. GoAccess is not really a good tool if you want to have statistics per months or per year, it does not do it.

5. Going further §

GoAccess can also generate a live report with --real-time-html, updated through a WebSocket while it reads the log, this can be useful sometimes.

It can also show the countries of the visitors if you give it a GeoIP database, this is not super accurate but can be useful.

GoAccess man page