Install GoAccess: Real-time Nginx Log Analysis on VPS

Install GoAccess: Real-time Nginx Log Analysis on VPS

August 2026 · 8 min read

GoAccess is an open-source web log analyzer that works entirely in-memory — no Elasticsearch, no Logstash, no Kibana required. It runs as an interactive terminal dashboard or exports a self-contained HTML report with optional WebSocket live updates. I've run it on lightweight VPS instances for years: under 10 MB RAM, instant startup, and it immediately shows me which bots are hammering my server, which endpoints are throwing 404s, and whether traffic patterns look normal. For small-to-medium VPS deployments, it's the perfect lightweight alternative to the full ELK stack.

Why GoAccess Instead of ELK or Grafana Loki?

FactorGoAccessELK StackGrafana Loki
RAM Usage< 10 MB2–8 GB+500 MB+
SetupOne commandComplexModerate
Real-time✅ WebSocket✅ (Kibana)✅ (Grafana)
CostFreeFree, but high spec requiredFree, but high spec required
Best ForSmall–medium VPSEnterprise / large-scale loggingMulti-service observability

Install GoAccess on Ubuntu/Debian

Ubuntu 22.04/24.04 ships an older GoAccess version in its default repositories. Use the official repo for the latest release:

# Add GoAccess official repository
echo "deb https://deb.goaccess.io/ $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/goaccess.list
wget -O - https://deb.goaccess.io/gnugpg.key | gpg --dearmor | sudo tee /usr/share/keyrings/goaccess.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/goaccess.gpg] https://deb.goaccess.io/ $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/goaccess.list

sudo apt update
sudo apt install -y goaccess

# Verify
goaccess --version

Install on CentOS/Rocky Linux/AlmaLinux

# Install from EPEL
sudo dnf install -y epel-release
sudo dnf install -y goaccess

goaccess --version

Understanding Your Nginx Log Format

Before running GoAccess you need to know what log format Nginx uses. Check /etc/nginx/nginx.conf:

# View the log_format directive
grep -A5 "log_format" /etc/nginx/nginx.conf

# Default combined format:
# $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"

# Find access_log paths across all configs
grep "access_log" /etc/nginx/nginx.conf /etc/nginx/sites-enabled/* 2>/dev/null | head -10

Run GoAccess Real-time in Terminal

The fastest way to get started — open a terminal and run:

# Analyze Nginx access log with COMBINED format
sudo goaccess /var/log/nginx/access.log \
  --log-format=COMBINED \
  -c

# Skip the format dialog and specify directly
sudo goaccess /var/log/nginx/access.log \
  --log-format=COMBINED \
  --no-query-string

# Analyze multiple log files (current + rotated)
sudo goaccess /var/log/nginx/access.log /var/log/nginx/access.log.1 \
  --log-format=COMBINED

The terminal dashboard is divided into panels: Visitors, Requests, Static Requests, Not Found (404s), Hosts, Operating Systems, Browsers, Time Distribution, and Virtual Hosts.

Tip: Press F5 to refresh, number keys (1–9) to expand any panel, q to quit.

Generate an HTML Dashboard with Cron

The feature I use most — export a self-contained HTML report viewable in any browser, with optional WebSocket live updates:

Static HTML Report

# Create the output directory
sudo mkdir -p /var/www/html/analytics
sudo chown www-data:www-data /var/www/html/analytics

# Generate report
sudo goaccess /var/log/nginx/access.log \
  --log-format=COMBINED \
  --output=/var/www/html/analytics/index.html \
  --no-query-string

ls -lh /var/www/html/analytics/index.html

Auto-update via Cron (every hour)

# Edit root crontab
sudo crontab -e

# Add this line
0 * * * * goaccess /var/log/nginx/access.log --log-format=COMBINED --output=/var/www/html/analytics/index.html --no-query-string 2>/dev/null

Protect the Dashboard with HTTP Basic Auth

# Create password file
sudo apt install -y apache2-utils
sudo htpasswd -c /etc/nginx/.analytics-htpasswd admin

# Add location block in Nginx config
location /analytics/ {
    auth_basic "Analytics";
    auth_basic_user_file /etc/nginx/.analytics-htpasswd;
    alias /var/www/html/analytics/;
}

# Reload Nginx
sudo nginx -t && sudo systemctl reload nginx

Real-time WebSocket Server Mode

GoAccess can run as a daemon that pushes live updates via WebSocket, so the HTML dashboard refreshes automatically without a page reload:

# Run GoAccess daemon with WebSocket
sudo goaccess /var/log/nginx/access.log \
  --log-format=COMBINED \
  --output=/var/www/html/analytics/index.html \
  --real-time-html \
  --ws-url=wss://yourdomain.com/ws \
  --port=7890 \
  --daemonize \
  --pid-file=/var/run/goaccess.pid \
  --log-file=/var/log/goaccess.log

# Verify daemon is running
ps aux | grep goaccess

Nginx WebSocket Proxy Config

# Add inside your Nginx server block
location /ws {
    proxy_pass http://127.0.0.1:7890;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    proxy_set_header Host $host;
    proxy_read_timeout 7200;
}

Filtering and Custom Log Formats

# Analyze only today's requests (pipe from grep)
grep "27/Aug/2026" /var/log/nginx/access.log | \
  sudo goaccess --log-format=COMBINED -

# Inspect suspicious IP range
grep "^185.220." /var/log/nginx/access.log | \
  sudo goaccess --log-format=COMBINED -

# Include all rotated gzip logs
(sudo zcat /var/log/nginx/access.log.*.gz 2>/dev/null; sudo cat /var/log/nginx/access.log) | \
  goaccess --log-format=COMBINED -o /var/www/html/analytics/index.html -

Custom Log Format

# If Nginx uses a non-standard format, specify it explicitly
sudo goaccess /var/log/nginx/access.log \
  --log-format='%h - %e [%d:%t %^] "%r" %s %b "%R" "%u" "%^"' \
  --date-format='%d/%b/%Y' \
  --time-format='%H:%M:%S'

Create a systemd Service for GoAccess Daemon

# Create service file
sudo tee /etc/systemd/system/goaccess.service << 'EOF'
[Unit]
Description=GoAccess Real-time Web Analytics
After=network.target nginx.service

[Service]
Type=forking
User=root
ExecStart=/usr/bin/goaccess /var/log/nginx/access.log \
  --log-format=COMBINED \
  --output=/var/www/html/analytics/index.html \
  --real-time-html \
  --ws-url=wss://yourdomain.com/ws \
  --port=7890 \
  --daemonize \
  --pid-file=/var/run/goaccess.pid \
  --log-file=/var/log/goaccess.log
ExecStop=/bin/kill -TERM $MAINPID
PIDFile=/var/run/goaccess.pid
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF

# Enable and start
sudo systemctl daemon-reload
sudo systemctl enable goaccess
sudo systemctl start goaccess
sudo systemctl status goaccess

Verify GoAccess Is Working Correctly

# Check version and build options
goaccess --version

# Dry-run to test log parsing without output
sudo goaccess /var/log/nginx/access.log \
  --log-format=COMBINED --dry-run 2>&1 | tail -5

# Check daemon service status
systemctl status goaccess

# Verify WebSocket port is listening
ss -tlnp | grep 7890

# Count total requests in current log
sudo wc -l /var/log/nginx/access.log
Tip: If GoAccess shows "Unknown log format," run sudo goaccess /var/log/nginx/access.log -c to open the format selection dialog — this tells you exactly which format matches your actual log entries.

Troubleshooting Common Errors

Error: "No input data was provided"

GoAccess can't read the log file — almost always a permissions issue.

# Check file permissions
ls -la /var/log/nginx/access.log

# Run with sudo
sudo goaccess /var/log/nginx/access.log --log-format=COMBINED

# Or add current user to adm group (Debian/Ubuntu)
sudo usermod -aG adm $USER
# Log out and back in

Error: "Couldn't parse log on date/time" or many invalid entries

The specified log format doesn't match the actual log file format. Inspect real log entries and adjust.

# View first 3 lines of the actual log
sudo head -3 /var/log/nginx/access.log

# Expected combined format:
# IP - user [dd/Mon/YYYY:HH:MM:SS +zone] "METHOD /path HTTP/x" status bytes "referer" "UA"

# Use custom format specifiers if needed
sudo goaccess /var/log/nginx/access.log \
  --log-format='%h %^ %^ [%d:%t %^] "%r" %s %b "%R" "%u"' \
  --date-format='%d/%b/%Y' \
  --time-format='%H:%M:%S'

Error: "WebSocket connection failed" on HTML dashboard

Nginx is missing the WebSocket proxy configuration, or the firewall is blocking GoAccess's port.

# Verify GoAccess daemon is listening
ss -tlnp | grep 7890

# Check Nginx config for WebSocket proxy
grep -rn "proxy_pass.*7890\|Upgrade" /etc/nginx/sites-enabled/

# Check firewall
sudo ufw status | grep 7890

Error: "Output file already opened by another instance"

Another GoAccess process is already writing to the same output file. Stop the old instance first.

# Find all GoAccess processes
ps aux | grep goaccess

# Stop via systemd
sudo systemctl stop goaccess

# Or kill directly
sudo kill $(cat /var/run/goaccess.pid)

sudo systemctl start goaccess

HTML report shows sparse data or empty panels

The log file just started, or rotated logs aren't included. Combine all rotated logs.

# Include all rotated gzip logs
(sudo zcat /var/log/nginx/access.log.*.gz 2>/dev/null; sudo cat /var/log/nginx/access.log) | \
  goaccess --log-format=COMBINED -o /var/www/html/analytics/index.html -

Frequently Asked Questions

What log formats does GoAccess support?

GoAccess supports COMBINED (Nginx/Apache combined log), COMMON (Apache common), VCOMBINED (when Nginx sits behind a load balancer that appends the real IP), W3C (IIS), CLOUDFRONT, CLOUDSTORAGE, AWSELB, SQUID, AWSS3, and fully custom formats defined with strftime-style specifiers. Most Nginx and Apache deployments work out of the box with --log-format=COMBINED.

Is the GoAccess HTML dashboard secure to expose publicly?

GoAccess processes everything in-memory — no database, no persistent data files (aside from the HTML output you explicitly generate). However, the HTML report does expose URL paths, partial IP addresses, and traffic statistics. Always protect the dashboard behind HTTP Basic Auth or restrict it to internal network access before exposing it externally.

How is GoAccess different from Matomo or Google Analytics?

GoAccess analyzes server-side access logs, so it captures every request including bots, crawlers, and API calls that browser-based analytics tools miss. However, it doesn't provide session tracking, conversion funnels, or detailed user journey data like Matomo does. Use GoAccess for server health monitoring and security analysis; use Matomo or similar tools for user behavior analytics.