GoAccess 1.12
Overview
GoAccess is an open-source web log analyser. It reads web server access logs (nginx, Apache, Amazon CloudFront, Elastic Load Balancing, Amazon S3, Caddy, Traefik, Squid and W3C formats). It turns them into a report of visitors, requested pages, static files, 404s, referrers, browsers, operating systems, status codes and time of day.
On this image, GoAccess runs in real-time mode. The report is a single HTML page whose panels update live over a WebSocket as new lines reach the log.
This guide covers launching and using the SecureAnyCloud GoAccess 1.12 on Ubuntu 24.04 AMI on Amazon EC2. It is one version of the SecureAnyCloud BI and web analytics listing on AWS Marketplace. You choose it as the software version when you subscribe and launch.
GoAccess's report has no login of its own, and its WebSocket server would answer anyone who can reach it. The SecureAnyCloud image makes it safe to run on EC2.
What you get:
- A password on the report and its live updates, unique to your instance. nginx asks for a user name and password (HTTP basic authentication) for the report page and for its WebSocket. The password is generated on your instance's first boot into a root-only file. No password, certificate, key or token secret is baked into the image. Change the password at any time with
goaccess-set-password. - A token on every WebSocket connection. GoAccess listens for WebSocket connections on
127.0.0.1:7890only, behind nginx. It checks a signed token (a JWT,--ws-auth) on every connection. The signing secret is generated on first boot. The token in the report is valid for 24 hours, and a fresh one is issued every 8 hours. If the report stops updating, reload the page. - Rate-limited per client. Every request carries the password, so nginx limits requests per client (5 a second, bursts of 20) and open WebSocket connections per client (8). It answers
429beyond that. The limit is per IPv4 address or per IPv6 /64 network, not per account. ForgedX-Forwarded-Forheaders do not move it. - HTTPS from the first boot. nginx serves the report on port 443 with a certificate generated on your instance's first boot for its public address. Port 80 redirects to HTTPS. Requests for a host name the instance does not serve get no answer.
- GoAccess runs unprivileged. It runs as its own
goaccessaccount with no Linux capabilities, reads logs through theadmgroup and can write only its report and its database. Other local accounts (includingubuntu) cannot connect to its WebSocket port. - Logged, without secrets. nginx's access log records failed and limited requests by path only. A failed sign-in appears as
401, a refused WebSocket as401or403, and a rate-limited request as429. The log never contains a query string (the WebSocket token travels in one) or a user name. nginx's error log for the report is kept atcrit, so no credentials or tokens are written to disk. A working WebSocket connection is not logged, so the report does not count itself. - Other websites cannot use your session. nginx refuses a WebSocket from another site's page, and anything other than reading (
GETandHEAD). It also sends a content security policy that allows scripts and connections to the instance only. - No outbound connections. GoAccess and nginx need no network access beyond the instance itself. On every boot, the kernel refuses the
goaccessandwww-dataaccounts every connection except to localhost. This includes the EC2 instance metadata service, your VPC and the internet. - Built from the pinned upstream release. GoAccess 1.12 is compiled unmodified from the upstream release tarball, checked against its SHA-256, with hardening flags (PIE, full RELRO, stack protector, FORTIFY_SOURCE). Ubuntu's own older package is not used.
- Fully patched Ubuntu 24.04 LTS at build time.
- Image contents: GoAccess 1.12 (
/usr/local/bin/goaccess), nginx, the options file/etc/goaccess/sac.conf, the toolsgoaccess-set-password,goaccess-set-domain,goaccess-rotate-secretandgoaccess-selftest, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent. No GeoIP database is included (Step 8). - Defaults: the default user is
ubuntu, the architecture is x86_64 and the recommended instance type is m5.large. - 24/7 SecureAnyCloud support
Out of the box, the report analyses nginx's own access log on the instance. Step 7 shows how to add your web servers' logs.
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair, and a VPC subnet with internet access.
- A security group that allows TCP 22 (SSH) and TCP 443 (the report) from your own network. Also allow TCP 80 (redirects to HTTPS) if you want plain
http://links to work. - Open port 443 only to the people who should read your traffic statistics.
Launch the instance
In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick GoAccess 1.12 on Ubuntu 24.04 as the software version, choose your Region, then choose Launch through EC2.
Choose m5.large, your key pair, a subnet and a security group as described in Prerequisites. A smaller instance such as t3.small handles logs of a few million lines well. An Elastic IP keeps the address, and so the certificate's name, stable across a stop and start.
To launch with the AWS CLI instead, replace the values in angle brackets:
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
--instance-type m5.large --key-name <your-key-pair> \
--security-group-ids <your-security-group> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=goaccess}]'Connect to your instance
Connect over SSH as the ubuntu user, which is the SSH user for GoAccess 1.12 on Ubuntu 24.04:
ssh -i <your-key.pem> ubuntu@<public-ip>Check the services
First boot reads the instance's public address from EC2 instance metadata. It generates the password, the WebSocket secret and the certificate, and it writes the credentials file. Then the egress backstop loads, GoAccess and nginx start, and the timer that refreshes the report's token is armed:
systemctl is-active sac-goaccess-egress goaccess nginx goaccess-refresh.timer
sudo test -f /var/lib/sac/data/goaccess-firstboot.done && echo FIRSTBOOT_DONEAll four units are active and the first boot marker is present:
active active active active FIRSTBOOT_DONEOnly SSH and nginx listen on the network. GoAccess's WebSocket server listens on localhost only:
sudo ss -lntpH | awk '{print $4, $6}' | sed -E 's/users:\(\("([^"]+)".*/\1/' | grep -v '127.0.0.5' | sort -u0.0.0.0:22 sshd 0.0.0.0:443 nginx 0.0.0.0:80 nginx 127.0.0.1:7890 goaccess [::]:22 sshd [::]:443 nginx [::]:80 nginxGet your URL and password
sudo cat /root/goaccess-credentials.txtOnly root can read the file. It holds the URL, the user name, the password and the certificate's fingerprint:
# GoAccess 1.12 (sac) on this instance. Open GOACCESS_URL in a browser and sign in as GOACCESS_USERNAME / GOACCESS_PASSWORD.
# The password is unique to this instance. Change it:
sudo goaccess-set-password (or --stdin to choose one).
# The certificate is self-signed for this instance: compare its SHA-256 fingerprint with GOACCESS_TLS_SHA256.
# The report analyses /var/log/nginx/access.log in real time; add your own logs in /etc/goaccess/sac.conf. GOACCESS_URL=https://<public-ip>/ GOACCESS_HOST=<public-ip> GOACCESS_USERNAME=admin GOACCESS_PASSWORD=<GOACCESS_PASSWORD> GOACCESS_TLS_SHA256=<64 hex digits, colon separated> GOACCESS_ADDRESS_SOURCE=imdsv2-public-ipv4 GUIDE=https://secureanycloud.com/stacks/goaccess-1-12/GOACCESS_ADDRESS_SOURCE says where the address came from. It is either the public IPv4 address from instance metadata or, when the instance has no public address, the private address.
The image includes a self test. It runs through nginx, pinned to this instance's certificate, and checks the following:
- The report and other paths refuse a request with no password and one with a wrong password.
- The password opens the report.
- The WebSocket refuses a request with no password, a wrong password, no token, a forged token or another site's origin.
- The WebSocket works with the report's own token.
Run the self test:
sudo goaccess-selftestgoaccess-selftest: 5 paths x (no credential, wrong password) -> 401; the password opens the report; WebSocket: no credential / wrong password / no JWT / forged JWT -> 401, cross-origin -> 403, the report's JWT -> 101 + data GOACCESS_SELFTEST_OKSign in
Open GOACCESS_URL in your browser. The certificate is self-signed for your instance, so the browser warns once. To check that you are talking to your instance, open the certificate details and compare its SHA-256 fingerprint with GOACCESS_TLS_SHA256. Then continue.
The browser asks for a user name and password. Enter admin and GOACCESS_PASSWORD. The browser remembers them until you close it.
On a new instance, the report shows only the requests nginx itself has served. At first, these are your own visits.
Read the report
The pictures in this guide show the report for a week of a fictional web shop's access log (shop.example.com, with documentation addresses only), so that every panel has something in it. The panels on your instance fill as your own logs are analysed (Step 7).
The overview at the top (the picture at the start of this guide) gives the totals for the period: requests, unique visitors, requested files, static files, 404s, bandwidth and the time GoAccess spent.
Below the overview, each panel has a chart and a table. Panel Options switches the chart type and the metric, and the arrows page through the rows. The menu on the left jumps to a panel and sets the layout and colour scheme.
Requested files lists the most requested paths with hits, unique visitors, bandwidth, method and protocol:
Not found URLs lists the paths that answered 404. Scans for admin pages and leaked files show up here:
Browsers and Operating systems count your visitors' software. GoAccess recognises crawlers and lists them separately:
HTTP status codes groups the responses by class. Expand a row to see the individual codes:
The other panels are visit time (hits per hour), virtual hosts, referring sites and URLs, static files, remote user and cache status. When you add a GeoIP database (Step 8), you also get geo location and ASN. Click a column heading to sort a table.
Requests in the report never carry a query string. GoAccess is started with --no-query-string, and this instance's own nginx log keeps the path only.
GoAccess keeps the data it has parsed in /var/lib/goaccess/db, so the report survives a restart or a reboot. It keeps 30 days of history by default.
Add your logs
The report reads the log files listed in /etc/goaccess/sac.conf:
sudo cat /etc/goaccess/sac.conf# sac GoAccess options. goaccess-serve reads this file as KEY=VALUE lines (it is never run as a shell script) and
# refuses to start GoAccess when a line is not valid. After a change:
sudo systemctl restart goaccess
#
# LOG_FORMAT: one of GoAccess's predefined formats: COMBINED VCOMBINED COMMON VCOMMON W3C CLOUDFRONT CLOUDSTORAGE AWSELB # SQUID AWSS3 CADDY AWSALB TRAEFIKCLF. This instance's nginx writes COMBINED (the request path without its query string). LOG_FORMAT=COMBINED
# LOG_FILE: one log file per LOG_FILE line (repeat the key for more files): an absolute path under /var/log/, a regular
# file (never a symbolic link) that the adm group can read. Every file must use LOG_FORMAT. LOG_FILE=/var/log/nginx/access.log
# KEEP_LAST: days of history kept in GoAccess's database (/var/lib/goaccess/db), 1 to 3650. KEEP_LAST=30 # ANONYMIZE_IP: yes masks the last octet of IPv4 and the last 80 bits of IPv6 client addresses in the report. ANONYMIZE_IP=no # GEOIP_DATABASE: an optional MaxMind / DB-IP .mmdb file you supply, root-owned, in /etc/goaccess/geoip/. Empty = off. GEOIP_DATABASE=GoAccess reads each file as the goaccess account through the adm group. A log file must therefore be under /var/log/, be a regular file (not a symbolic link) and be readable by adm. All files in one report use the same LOG_FORMAT.
To analyse your web servers' logs, get them onto the instance:
- Put the logs under
/var/log, for example in/var/log/web/, owned by root and readable byadm. - Logs that keep arriving (from a web server on this instance,
rsyncor a log shipper) are analysed live. GoAccess follows each file as it grows.
sudo install -d -o root -g adm -m 0750 /var/log/web
sudo install -o root -g adm -m 0640 /tmp/access.log /var/log/web/access.logEdit the options file (sudo nano /etc/goaccess/sac.conf) as follows:
- Add a
LOG_FILE=/var/log/web/access.logline. Use one line per file, up to 32. - Set
LOG_FORMATto your logs' format. Apache and nginx in their default formats areCOMBINED. An Application Load Balancer isAWSALB, CloudFront isCLOUDFRONTand Amazon S3 isAWSS3. - Remove the
LOG_FILE=/var/log/nginx/access.logline if the formats differ.
Restart GoAccess. It checks the file first, and it refuses to start with a message that names the line that is wrong:
sudo systemctl restart goaccess systemctl is-active goaccessGoAccess adds what it reads to the history in its database. To start the report again from nothing, for example after switching to different logs, stop GoAccess, empty the database and start it:
sudo systemctl stop goaccess
sudo find /var/lib/goaccess/db -mindepth 1 -delete sudo systemctl start goaccessSet ANONYMIZE_IP=yes to mask the client addresses in the report, and KEEP_LAST to keep more or fewer days.
Add a GeoIP database (optional)
No GeoIP database ships on the image, so the geo location and ASN panels are off. GoAccess reads MaxMind's GeoLite2 or DB-IP's free databases in .mmdb format.
Download one. MaxMind's database needs a free account and has its own licence terms. Copy it to the instance, then run:
sudo install -o root -g root -m 0644 /tmp/GeoLite2-City.mmdb /etc/goaccess/geoip/GeoLite2-City.mmdbSet GEOIP_DATABASE=/etc/goaccess/geoip/GeoLite2-City.mmdb in /etc/goaccess/sac.conf and run sudo systemctl restart goaccess.
The databases change monthly. To update one, replace the file and restart GoAccess. GoAccess cannot download the database itself, because it has no outbound access (Step 12).
Change the password
To generate a new random password, run the command below. It writes the password to the credentials file as GOACCESS_PASSWORD:
sudo goaccess-set-passwordTo choose your own password, use 16 characters or more. The command reads it from standard input and does not store it on the instance:
sudo goaccess-set-password --stdinThe new password takes effect at once, and your browser asks for it again. The password is stored for nginx as a bcrypt hash in /etc/nginx/sac/goaccess.htpasswd.
Use your own domain, or a new address
The certificate, the host names nginx answers and the report's WebSocket address are set for the address the instance had on first boot. If you stop and start an instance that has no Elastic IP, its public address changes. Run goaccess-set-domain with the new address.
To use a domain, point an A record (for example stats.example.com) at the instance's public IP address, then run:
sudo goaccess-set-domain <your-domain>This command sets the name nginx accepts and generates a new self-signed certificate for it. It points the report's WebSocket at wss://<your-domain>:443/ws and restarts GoAccess and nginx. It also updates GOACCESS_URL, GOACCESS_HOST and GOACCESS_TLS_SHA256 in the credentials file.
To serve more names or addresses, add them after the first one (sudo goaccess-set-domain <your-domain> <public-ip>). Open the report at the first name.
For a certificate that your browser trusts, put the certificate chain for <your-domain> in /etc/ssl/goaccess/goaccess.crt. Put its private key in /etc/ssl/goaccess/goaccess.key (root only, mode 600). Then run sudo systemctl reload nginx.
The WebSocket token
The report page carries a token (a JWT signed with the instance's own secret) that its WebSocket presents to GoAccess. A token is valid for 24 hours.
The goaccess-refresh.timer restarts GoAccess every 8 hours, so the report always carries a token with hours to run. The data is kept across the restart. If the report has been open for a long time and stops updating, reload the page.
To make every token issued so far stop working at once, for example after someone who should no longer see the report has had it open, run:
sudo goaccess-rotate-secretgoaccess-rotate-secret: a new WebSocket secret is in place and GoAccess was restarted; reload the report pageRotate the secret together with the password (Step 9). A token alone cannot open the WebSocket, because nginx asks for the password first.
Outbound connections
GoAccess, nginx and the first-boot service are refused any connection to the instance metadata service. An iptables backstop, loaded on every boot, also refuses the goaccess and www-data accounts every connection except to localhost. This includes your VPC, private networks and the internet.
GoAccess needs no outbound connection, because it reads only the log files on the instance. Only root, goaccess and nginx (www-data) may connect to GoAccess's port on localhost.
Security checks
These checks show that the password guards the report and its WebSocket, and that anything other than reading is refused. They also show that a host name the instance does not serve gets no answer, that plain HTTP redirects, that other local accounts cannot reach GoAccess directly, and that the goaccess account cannot reach the metadata service or the internet:
H=$(sudo sed -n 's/^GOACCESS_HOST=//p' /root/goaccess-credentials.txt); WRONG="admin:not-the-password" curl -sk -o /dev/null -w 'no password: %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/" curl -sk -o /dev/null -w 'wrong password: %{http_code}\n' --resolve "$H:443:127.0.0.1" -u "$WRONG" "https://$H/" curl -sk -o /dev/null -w 'WebSocket without a password: %{http_code}\n' --resolve "$H:443:127.0.0.1" -H 'Connection: Upgrade' -H 'Upgrade: websocket' -H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' "https://$H/ws" curl -sk -o /dev/null -w 'POST: %{http_code}\n' --resolve "$H:443:127.0.0.1" -X POST "https://$H/" curl -sk -o /dev/null -w 'unknown host name: %{http_code}\n' --resolve evil.example:443:127.0.0.1 https://evil.example/ || true curl -s -o /dev/null -w 'http: %{http_code}\n' --resolve "$H:80:127.0.0.1" "http://$H/" curl -s -o /dev/null -w 'GoAccess directly, as ubuntu: %{http_code}\n' -m 5 http://127.0.0.1:7890/ || true
sudo -u goaccess curl -s -m 5 -o /dev/null -w 'metadata from the goaccess account: %{http_code}\n' http://169.254.169.254/latest/meta-data/ || true
sudo -u goaccess curl -s -m 5 -o /dev/null -w 'internet from the goaccess account: %{http_code}\n' https://secureanycloud.com/ || trueno password: 401 wrong password: 401 WebSocket without a password: 401 POST: 405 unknown host name: 000 http: 301 GoAccess directly, as ubuntu: 000 metadata from the goaccess account: 000 internet from the goaccess account: 000GoAccess and nginx are blocked from the instance metadata service and reserved addresses. The backstop refuses everything else that is not localhost:
for u in goaccess nginx; do printf '%s ' "$u"; systemctl show -p IPAddressDeny --value "$u" | tr ' ' '\n' | sort | tr '\n' ' '; echo; done
sudo iptables -S SAC-GOACCESS-EGRESSgoaccess 168.63.129.16/32 169.254.169.254/32 192.0.0.0/24 198.18.0.0/15 2001::/32 2002::/16 224.0.0.0/4 240.0.0.0/4 64:ff9b:1::/48 64:ff9b::/96 ::ffff:0.0.0.0/96 fd00:ec2::254/128 nginx 168.63.129.16/32 169.254.169.254/32 192.0.0.0/24 198.18.0.0/15 2001::/32 2002::/16 224.0.0.0/4 240.0.0.0/4 64:ff9b:1::/48 64:ff9b::/96 ::ffff:0.0.0.0/96 fd00:ec2::254/128 -N SAC-GOACCESS-EGRESS -A SAC-GOACCESS-EGRESS -d 127.0.0.0/8 -o lo -j RETURN -A SAC-GOACCESS-EGRESS -p tcp -j REJECT --reject-with tcp-reset -A SAC-GOACCESS-EGRESS -j REJECT --reject-with icmp-admin-prohibitedGoAccess runs as goaccess on localhost, with no Linux capabilities, and it cannot gain any. It checks a token on every WebSocket connection (--ws-auth):
P=$(systemctl show -p MainPID --value goaccess) ps -o user:9=,args= -p "$P"
sudo grep -E '^(CapEff|CapBnd|CapAmb|NoNewPrivs)' /proc/"$P"/statusgoaccess /usr/local/bin/goaccess --no-global-config --real-time-html --addr=127.0.0.1 --port=7890 --ws-url=wss://<public-ip>:443/ws --ws-auth=jwt:/etc/goaccess/ws-auth.secret --ws-auth-expire=24h --ping-interval=30 --output=/var/lib/goaccess/html/index.html --persist --restore --db-path=/var/lib/goaccess/db/ --keep-last=30 --log-format=COMBINED --no-query-string --html-report-title=GoAccess (sac) - <public-ip> --log-file=/var/log/nginx/access.log CapEff: 0000000000000000 CapBnd: 0000000000000000 CapAmb: 0000000000000000 NoNewPrivs: 1The credentials, the password hash, the TLS key and the WebSocket secret are readable only by root and the one service that needs each:
sudo stat -c '%a %U:%G %n' /root/goaccess-credentials.txt /etc/nginx/sac/goaccess.htpasswd /etc/ssl/goaccess/goaccess.key /etc/goaccess/ws-auth.secret /etc/goaccess/sac.conf600 root:root /root/goaccess-credentials.txt 640 root:www-data /etc/nginx/sac/goaccess.htpasswd 600 root:root /etc/ssl/goaccess/goaccess.key 640 root:goaccess /etc/goaccess/ws-auth.secret 644 root:root /etc/goaccess/sac.confOther notes:
- Rate limits. The burst of 20 covers normal use of the report. A script that requests the report in a tight loop from one address gets
429. - Client addresses. nginx replaces any
X-Forwarded-FororX-Real-IPa client sends with the address it connected from. - Logs. nginx's access log (
/var/log/nginx/access.log) records each request by path only, with no query string and no user name. Failed sign-ins appear as401, refused WebSocket connections as401or403, and rate-limited requests as429. nginx's error log for the report is kept atcrit. GoAccess logs to the journal (journalctl -u goaccess). - Response headers. nginx sends HSTS, a content security policy,
X-Content-Type-Options: nosniff,X-Frame-Options: DENYandReferrer-Policy: same-origin, and no version number. The content security policy allows'unsafe-eval'for scripts because the report's own templates and charts need it. Scripts are still loaded from the instance only. - NVMe host identity. No NVMe-oF host identity ships in the image. If you install
nvme-cli, its host NQN and host ID are your instance's own. - Who can reach it. Keep ports 22 and 443 open only to your own networks.
Back up
GoAccess's history is in /var/lib/goaccess/db, and your logs stay where you put them. To copy the database and the options to your computer, run:
ssh -i <your-key.pem> ubuntu@<public-ip> 'sudo tar -C / -czf - var/lib/goaccess/db etc/goaccess/sac.conf' > goaccess-backup.tar.gzAn EBS snapshot of the instance's volume is also a complete backup.
Keep it up to date
To apply Ubuntu updates now, run:
sudo apt-get update && sudo apt-get upgrade -yNew GoAccess releases arrive as new versions of this listing, built and checked the same way.
To move to a new version, launch it, copy your options file and logs to it, and restore the database from your backup. Stop GoAccess before you restore. Read the GoAccess release notes before you move.
Support and licensing
GoAccess is open source under the MIT licence. It is compiled unmodified from the upstream release. Its licence text, its release notes and a record of how it was built are in /usr/share/doc/goaccess, together with the licences of the components in its source tree:
ls /usr/share/doc/goaccessCOPYING NEWS README.sacThe source code is at github.com/allinurl/goaccess. GoAccess is not endorsed by or affiliated with SecureAnyCloud. There is no per-user licence fee.
The AMI price covers SecureAnyCloud's build, hardening, first-boot setup and support.
- Email: sales@secureanycloud.com
- Website: https://secureanycloud.com
- Support: 24/7, with a 24 hour response SLA
The other versions on this listing are on the BI and web analytics guide.
Deploy on AWS
Launch GoAccess 1.12 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com