Goaccess with caddy v2

Hello,

Looking around the forum I found several caddy + goaccess integration example, but none that works with caddy v2. Did anyone already managed to have this integration active, both for the log analysis and the websocket redirection ?

1. The problem I’m having:

Examples of integration of goaccess and caddy are relying on caddy v1

2. Error messages and/or full log output:

3. Caddy version:

v2.10.0

4. How I installed and ran Caddy:

a. System environment:

Linux, Ubuntu, systemd

b. Command:

PASTE OVER THIS, BETWEEN THE ``` LINES.
Please use the preview pane to ensure it looks nice.

c. Service/unit/compose file:

PASTE OVER THIS, BETWEEN THE ``` LINES.
Please use the preview pane to ensure it looks nice.

d. My complete Caddy config:

PASTE OVER THIS, BETWEEN THE ``` LINES.
Please use the preview pane to ensure it looks nice.

5. Links to relevant resources:

Just a quick example setup on my Mac:

Install Caddy via Homebrew:

brew install caddy
caddy version
v2.10.0 h1:fonubSaQKF1YANl8TXqGcn4IbIRUDdfAkpcsfI/vX5U=

Install GoAccess via Homebrew:

brew install goaccess
goaccess -V
GoAccess - 1.9.4.
For more details visit: https://goaccess.io/
Copyright (C) 2009-2024 by Gerardo Orellana

Build configure arguments:
  --enable-geoip=mmdb

File structure :

tree -L2 /REDACTED/Caddy
/REDACTED/Caddy
β”œβ”€β”€ data
β”‚   └── caddy
β”œβ”€β”€ etc
β”‚   └── Caddyfile
β”œβ”€β”€ logs
β”‚   └── access_log.json
β”œβ”€β”€ maxmind
β”‚   └── GeoLite2-Country.mmdb
└── www
    β”œβ”€β”€ index.html
    └── stats

Caddyfile

{
	http_port 8080
}

:8080 {
	root * /REDACTED/Caddy/www
	file_server
	log {
		output file /REDACTED/Caddy/logs/access_log.json {
			roll_size 10MB
			roll_keep 2
		}
		format json
	}
}

Run Caddy:

caddy run --config /REDACTED/Caddy/etc/Caddyfile

Run goAccess:

goaccess /REDACTED/Caddy/logs/access_log.json -o /REDACTED/Caddy/www/stats/index.html  --real-time-html --log-format=CADDY --geoip-database=/REDACTED/Caddy/maxmind/GeoLite2-Country.mmdb

Visit http://localhost:8080/stats/:

Obviously, tweak it as needed to fit your setup. It took me longer to write this up than to get it running :slight_smile:

And of course, I missed the important question you asked - the GoAccess WebSocket. Sorry about that!

Let’s assume your GoAccess stats site is stats.example.com, and your WebSocket will run on ws.example.com.

Caddyfile:

stats.example.com {
	root * /REDACTED/Caddy/www
	file_server
	log {
		output file /REDACTED/logs/access_log.json {
			roll_size 10MB
			roll_keep 2
		}
		format json
	}
}

ws.example.com {
    reverse_proxy 127.0.0.1:7890
}

Run goAccess:

goaccess /REDACTED/Caddy/logs/access_log.json -o /REDACTED/Caddy/www/index.html  --real-time-html --log-format=CADDY --geoip-database=/REDACTED/Caddy/maxmind/GeoLite2-Country.mmdb --ws-url=wss://ws.example.com:443

Note the --ws-url=wss://ws.example.com:443 option. That tells GoAccess to use the WebSocket endpoint, which is handled by the ws.example.com Caddy virtual host proxying to GoAccess.

1 Like

Alright, I’ve been playing around with GoAccess stats today and put together a full demo setup.

I’m using user ID 1050 and group ID 1050 in this example, so be sure to adjust the IDs, TZ, and paths as needed for your own environment.

Caddy Docker Compose:

services:
  caddy:
    image: timelordx/caddy-dns-acmedns:latest
    container_name: caddy
    environment:
      - TZ=PST8PDT
    volumes:
      - /volume/docker/caddy/etc/caddy:/etc/caddy:ro
      - /volume/docker/caddy/config:/config
      - /volume/docker/caddy/data:/data
      - /volume/docker/caddy/logs:/logs
      - /volume/docker/caddy/www:/www:ro
    network_mode: host
    restart: unless-stopped

MaxMind Docker Compose:

services:
  maxmind:
    image: ghcr.io/maxmind/geoipupdate
    container_name: maxmind
    user: "1050:1050"
    environment:
      - GEOIPUPDATE_ACCOUNT_ID=REDACTED
      - GEOIPUPDATE_LICENSE_KEY=REDACTED
      - 'GEOIPUPDATE_EDITION_IDS=GeoLite2-ASN GeoLite2-City GeoLite2-Country'
      - GEOIPUPDATE_FREQUENCY=72
    volumes:
      - /volume/docker/maxmind:/usr/share/GeoIP
    restart: unless-stopped

GoAccess Docker Compose:

services:
  goaccess:
    image: allinurl/goaccess
    container_name: goaccess
    user: "1050:1050"
    environment:
      - TZ=PST8PDT
    volumes:
      - /volume/docker/goaccess/db:/db
      - /volume/docker/caddy/logs:/logs:ro
      - /volume/docker/caddy/www/stats:/www
      - /volume/docker/maxmind:/maxmind:ro
    command:
      - '--geoip-database=/maxmind/GeoLite2-City.mmdb'
      - '--log-file=/logs/access.log'
      - '--log-format=CADDY'
      - '--db-path=/db'
      - '--persist'
      - '--restore'
      - '--real-time-html'
      - '--output=/www/index.html'
      - '--ws-url=wss://ws.example.com:443'
    ports:
      - 127.0.0.1:7890:7890
    restart: unless-stopped

Caddyfile:

########################################
## GLOBAL OPTIONS
########################################

{
	email acme@example.com
}

########################################
## SNIPPETS
########################################

(default_imports) {
	encode gzip zstd
	## a bunch of headers and other optional stuff
}

########################################
## Default wildcard VIRTUAL used solely 
## to obtain the wildcard certificate
########################################

*.example.com {

	## TLS settings (ACMEDNS)
	tls {
		dns acmedns /etc/caddy/creds/acmedns-example.com.json
	}

	## Full Lockdown – Do Not Serve Any Content
	abort
}

########################################
webstats.example.com {
########################################
	
	import default_imports

	@allowed_ips {
		remote_ip private_ranges
	}
	handle @allowed_ips {
		root * /www/stats/
		file_server
	}
	handle {
		abort
	}
}

########################################
ws.example.com {
########################################
	
	import default_imports

	@allowed_ips {
		remote_ip private_ranges
	}
	handle @allowed_ips {
		reverse_proxy 127.0.0.1:7890
	}
	handle {
		abort
	}
}

########################################
## A couple of websites that generate 
## log output to /logs/access.log
########################################

...
...
...

########################################
## Drop all HTTP requests for otherwise 
## unhandled sites
########################################

http:// {
	abort
}

In general:

  • MaxMind keeps the GeoIP database up to date
  • GoAccess listens only on the loopback interface
  • I’m using my own timelordx/caddy-dns-acmedns Docker image just for ACME-DNS support - this is only relevant to how I obtain TLS certificates
  • Caddy restricts access to the stats interface to local networks
  • Stats are served at webstats.example.com, with the WebSocket endpoint at ws.example.com

You can easily adjust it all to your needs.

1 Like

@timelordx, would you please copy those posts into a post under wiki category?

2 Likes

Sounds good! :+1: I’ll clean it up this week and post it as a wiki article.

2 Likes

Would it be possible to use this without your custom caddy build / with the regular image or is it too specific ? (not sure what in your configuration required the custom image)

You definitely can use the regular Caddy image.

As I mentioned in my previous post:

I’m using my own timelordx/caddy-dns-acmedns Docker image just for ACME-DNS support - this is only relevant to how I obtain TLS certificates

I simply copy-pasted my running setup as an example. In that setup, I’m using a wildcard certificate obtained via the ACME-DNS module, which is the only reason for the custom image.

That said, I’ve since simplified the configuration and removed the separate WebSockets site. Everything is now served under a single domain:

1 Like