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:
timelordx
(timelordx)
June 10, 2025, 1:01am
2
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
timelordx
(timelordx)
June 10, 2025, 1:47am
3
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
timelordx
(timelordx)
June 10, 2025, 7:23am
4
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
Mohammed90
(Mohammed Al Sahaf)
June 10, 2025, 9:37am
5
@timelordx , would you please copy those posts into a post under wiki category?
2 Likes
timelordx
(timelordx)
June 10, 2025, 7:18pm
6
Sounds good! 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)
timelordx
(timelordx)
June 11, 2025, 8:39pm
8
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:
This setup lets you analyze Caddy access logs with GoAccess and view real-time web stats via webstats.example.com, served directly by your Caddy server. It also supports WebSockets through the same domain.
Components (Docker Images) Used:
Caddy web server
GoAccess
MaxMind GeoIP (optional)
Caddy Docker Compose:
services:
caddy:
image: caddy:latest
container_name: caddy
volumes:
- /volume/docker/caddy/etc/caddy:/etc/caddy:ro
- /volume/docker/caddy/config:/config
β¦
1 Like
system
(system)
Closed
July 11, 2025, 8:40pm
9
This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.