guard
Tail a log source and auto-block IPs that exceed thresholds. Normal runs modify the firewall — every run can add iptables/nftables/DOCKER-USER rules. Use --dry-run to execute detection without firewall or state-file changes.
guard runs need sudo; --dry-run does not. Test with --never-block YOUR_IP or run blocklist refresh first.Synopsis
caddy-analyze guard [source] [flags]
# source: file, docker://name, k8s://pod -n ns, journalctl://unit, or via caddy-analyzer.json
# see Log Sources for syntax — this page covers guard flags only
Flags
| Flag | Short | Default | Description |
|---|---|---|---|
--limit | -l | 100 | Max HTTP requests per IP in window before block |
--window | -w | 1m | Sliding window (per-second buckets) |
--duration | -d | 10m | Ban duration (0 = permanent) |
--auth-limit | 10 | 401/403 before block | |
--notfound-limit | 50 | 404 before block | |
--detect-confidence | 8 | Min confidence 1-10 for pattern-based blocks (0 disables only this block path; it is not a dry run) | |
--firewall-backend | auto | auto|iptables|docker|nftables|hybrid | |
--dry-run | false | Report would-be blocks and emit would_block audit events without changing firewall or state | |
--iptables-timeout | 10s | Per-invocation iptables timeout | |
--country-block | ISO codes to block immediately (e.g. CN,RU) | ||
--no-blocklist | false | Disable 8 blocklist feeds | |
--blocklist-refresh | 6h | Refresh interval (min 1h, 0 off) | |
--cache-dir | ~/.cache/…/blocklists | Blocklist cache dir | |
--geoip-db | GeoIP mmdb path (auto-discovery) | ||
--no-auto-download | false | Disable GeoIP auto-download | |
--never-block | CIDRs/IPs never block (protect your IP!) | ||
--never-block-file | File with CIDRs (one per line, # comment) | ||
--protect-self | true | Discover and protect server interface/public egress IPs; --protect-self=false disables these checks | |
--audit-log | /var/log/…-audit.jsonl | JSON lines audit (0600) | |
--audit-syslog | Forward audit events to a syslog UDP address | ||
--webhook-url / --audit-webhook | Async webhook notification; provider: generic, slack, discord, pagerduty | ||
--pagerduty-routing-key | PagerDuty routing key (required for PagerDuty, never logged) | ||
--audit-timeout / --audit-retries | 5s / 2 | Per-delivery timeout and bounded retries | |
--audit-rate-limit | 0 | Minimum notification interval per IP | |
--state-file | /var/lib/…/blocked.json | Persist blocks across restarts | |
--subnet-limit | 0 | /24 or /64 distributed scan threshold | |
--rps-anomaly | 0 | EWMA RPS spike factor (audit alert only; requires --audit-log) | |
--cred-stuffing-limit | 0 | Distinct IPs failing same path (audit alert only; requires --audit-log) | |
--ua-rotation | 10 | UA rotation threshold | |
--trust-forwarded | false | Use last public XFF hop |
Automatic self-ban protection
Since v0.8.0, --protect-self=true protects the server's interface and public egress addresses by default. This includes requests from processes sharing the same home router/NAT. Discovery runs at startup, every 30 seconds and again before each new ban, so a WAN address change is checked before blocking. A successful refresh replaces old addresses; it does not append entries to the whitelist file.
Protection takes precedence over request/auth/404 thresholds, pattern detection, blocklist feeds, country rules and subnet bans containing a server address. Existing tracked rules covering a protected address are removed from the firewall and saved state. Failed removals remain tracked for retry. Dry-run reports would_unblock without changing either.
Public discovery uses direct, family-specific HTTPS requests to ipify, with icanhazip as fallback. HTTP proxy settings are ignored. Each request has a 4-second timeout and discovery has a 10-second overall limit. A failure in any required family, including IPv6 configured without Internet connectivity, fails the check. Before-ban checks are synchronous and add latency.
If discovery fails at startup, guard refuses to start; runtime failures retain known protected addresses and suspend new bans until discovery recovers. Both errors instruct you to restart with --protect-self=false. See Troubleshooting → server IP protection.
On a remote VPS, the administrator's home/mobile IP is usually different from the server's IP: add it separately with --never-block or whitelist. Shared-NAT sources receive the same public-IP exemption; a different device's IPv6 address is not automatically protected. Run guard on the same network/egress path as the processes being protected. This feature does not change Fail2Ban or prevent explicit manual block commands.
Minimal example
modifies firewall — server addresses are protected automatically. For remote administration, also allowlist the client IP.
# Replace this with the public IP of your administrator workstation, not the VPS
ADMIN_IP=203.0.113.10
sudo caddy-analyze guard --never-block "$ADMIN_IP" --limit 50 --window 1m /var/log/caddy/access.log
Fixed-IP or restricted-network server
modifies firewall — disable server-IP discovery and keep explicit trusted addresses. This skips both interface and public-IP checks, not the explicit whitelist.
sudo caddy-analyze guard /var/log/caddy/access.log \
--protect-self=false --never-block 203.0.113.10
Advanced example
modifies firewall — hybrid backend + country + persistent state. To safely preview the same logic, add --dry-run; the terminal shows would-be blocks and the audit sink receives would_block events.
# hybrid = INPUT + DOCKER-USER; blocks survive restart; full audit
sudo caddy-analyze guard docker://caddy \
--firewall-backend hybrid \
--country-block CN,RU,IR \
--never-block 10.0.0.0/8,192.168.1.50 \
--never-block-file /etc/caddy-analyzer/whitelist.txt \
--audit-log /var/log/caddy-analyzer-audit.jsonl \
--state-file /var/lib/caddy-analyzer/blocked.json \
--blocklist-refresh 6h \
--detect-confidence 8
# verify what was blocked:
sudo caddy-analyze unban --list
cat /var/log/caddy-analyzer-audit.jsonl | tail
--never-block. Use --dry-run to preview decisions; it still performs self-IP discovery unless disabled. Whitelist-file changes require restarting guard.