If you're running Caddy v2 in production, you've probably noticed something: the standard log analysis tools don't work. GoAccess, lnav, even grep — they all choke on Caddy's nested JSON schema. You end up writing brittle one-off scripts to parse TLS versions, nested request headers, and float durations.
I got tired of that. So I built caddy-analyzer.
What is it?
caddy-analyzer is a CLI tool (written in Go) that natively parses Caddy v2 structured JSON logs and layers a full security detection engine on top. Think of it as goaccess + fail2ban + threat intelligence, purpose-built for Caddy.
The problem with Caddy logs
Caddy doesn't use Common/Combined Log Format. It outputs structured JSON like this:
{
"request": {
"method": "GET",
"uri": "/search?id=1' OR '1'='1",
"remote_ip": "192.0.2.42",
"proto": "HTTP/2.0"
},
"http": {
"status_code": 403,
"duration": 0.0023
},
"tls": {
"version": "tls1.3",
"cipher_suite": "TLS_AES_256_GCM_SHA384"
}
}
Existing tools expect flat log lines. They can't handle nested maps, so you're stuck writing custom parsers. And more importantly: none of them detect attacks.
What caddy-analyzer does
Security detection — 26 categories
Every request is scanned against 26 attack categories using a dual-pass engine (URL-unescaped + raw URI matching) to catch multibyte and double-encoded bypass attempts:
| Category | What it catches |
|---|---|
| SQL Injection | UNION SELECT, blind injection, DB fingerprinting |
| XSS |
<script>, event handlers, data:text/html
|
| SSRF | Cloud metadata (169.254.169.254), loopback IPs, protocol smuggling |
| RCE | Shell injection, reverse shells, eval(), deserialization |
| Path Traversal / LFI |
../, null byte, /etc/passwd, php://input
|
| SSTI | Jinja2, Freemarker, ERB, Thymeleaf template injection |
| Log4j / JNDI |
${jndi:ldap://, obfuscated variants |
| XXE / XInclude | XML entity expansion, external DTD |
| CRLF Injection | Header injection, log poisoning |
| ...and 17 more | GraphQL, LDAP, XPath, JWT abuse, beaconing/C2, scanner detection |
Every detection is tagged with MITRE ATT&CK technique IDs. Results are grouped by offending IP with request details.
Real-time IP blocking
The guard subcommand monitors your log stream and auto-blocks malicious IPs via iptables:
sudo caddy-analyze guard docker://my-caddy
Features:
- Sliding window rate limiting (per-IP, per-second buckets)
- 8 default blocklist feeds (Spamhaus, FireHOL, CINS, Tor exit nodes, Emerging Threats, AbuseIPDB)
-
GeoIP country blocking:
--country-block CN,RU,IR - Audit logging and state persistence across restarts
- Subnet limiting for distributed scan defense
Live threat highlighting
Stream logs in real-time with inline threat detection:
caddy-analyze tail --detect docker://my-caddy
21:06:07 404 WARN GET /cms/gather/getArticle (1.71 KB, 2.27ms) - 2.58.137.2 [macOS/Safari] → XSS · RCE
21:06:07 404 WARN GET /wp-content/plugins/restropress/readme.txt (9 B, 73µs) - 2.58.137.2 [Linux/Firefox] → WP
21:06:06 200 OK GET / (3.04 KB, 5.95ms) - 2.58.137.2 [macOS/Firefox]
IPs are colored by severity (red = critical, amber = medium, olive = low). Clean entries show no markers — zero visual noise.
Everything else
-
TUI dashboard (
--watch) — 8-tab live interface with security alerts, GeoIP, operational events - HTML reports — standalone dark-mode reports for sharing with your team
- Diff engine — compare two log files to detect 5xx spikes and latency regressions
- GeoIP enrichment — offline mmdb lookup, auto-download, no API key needed
- Sigma export — export detection rules as Sigma YAML for SIEM import
-
Multi-source — files, Docker (
docker://), K8s (k8s://), journalctl (journalctl://) - Self-update with cosign signature verification
Quick start
# Install (Linux/macOS)
curl -sSfL https://raw.githubusercontent.com/lenny-ts/caddy-analyzer/main/install.sh | bash
# Set your Caddy log path
caddy-analyze config /var/log/caddy/access.log
# Full security scan
caddy-analyze --detect
# Real-time streaming with detection
caddy-analyze tail --detect docker://my-caddy
# Launch interactive dashboard
caddy-analyze --watch
Or try it with the included sample logs (no live Caddy needed):
git clone https://github.com/lenny-ts/caddy-analyzer.git
cd caddy-analyzer
caddy-analyze --detect testdata/sample.log
Why not just use goaccess/lnav/grep?
| Capability | caddy-analyzer | goaccess | lnav | grep/awk |
|---|---|---|---|---|
| Caddy v2 JSON native | ✅ | ❌ | ❌ | ❌ |
| Security threat detection (26 categories) | ✅ | ❌ | ❌ | ❌ |
| Real-time firewall guard (iptables) | ✅ | ❌ | ❌ | ❌ |
| Dual-pass evasion-resistant detection | ✅ | ❌ | ❌ | ❌ |
| Comparative diff engine | ✅ | ❌ | ❌ | ❌ |
| TUI dashboard | ✅ | ✅ | ✅ | ❌ |
| Multi-source (Docker, K8s, journalctl) | ✅ | ❌ | ✅ | ❌ |
| GeoIP enrichment (offline) | ✅ | ❌ | ❌ | ❌ |
Performance
Tested on a single core with synthetic logs (10% attack traffic):
| Log size | --detect |
Parse only | RAM |
|---|---|---|---|
| 1.5K lines (real) | 0.6s | <0.1s | 21 MB |
| 10K lines | 1.5s | 0.2s | 25 MB |
| 100K lines | 14.3s | 1.4s | 53 MB |
| 1M lines | 2m29s | ~14s | 138 MB |
~7,000 lines/sec with detection, ~70,000 lines/sec parse-only.
Try it out
The repo ships sample logs with all 26 attack categories using TEST-NET IPs (RFC 5737) — no real hosts involved:
caddy-analyze --detect testdata/sample.log # curated sample
caddy-analyze --detect testdata/large.log # 50K lines, ~27MB
caddy-analyze -f html -o report.html --detect testdata/sample.log # HTML report
Contributing
PRs and issues welcome! See CONTRIBUTING.md.
GitHub: github.com/lenny-ts/caddy-analyzer
If you find it useful, a ⭐ on GitHub would mean a lot. It helps other Caddy users discover the tool.
Written with ❤️ for the Caddy community

Top comments (0)