http-sentinel #
A single nginx PREACCESS module that scores every client and acts on the verdict — tarpit bots, block scanners, PoW-challenge grey-area requests, feed decisions back to CrowdSec. No network in the request path, no external process, no added latency.
Source: our fork on GitHub
Directives
sentinel #
syntax: sentinel on | off (on/off flag); · context: location
Ships as libnginx-mod-http-sentinel in the deb.myguard.nl nginx / angie build. --- ## How it works Every request is scored by combining weighted signals — bot UA, error burst, scanner paths, velocity, datacenter ASN, TLS/TCP fingerprints, CrowdSec decisions.
sentinel_allowlist #
syntax: sentinel_allowlist 1+ args; · context: http, server, location
sentinel_allowlist <ip|cidr> [ip|cidr ...]; — operator-trusted ranges
sentinel_asn #
syntax: sentinel_asn 1 arg; · context: http, server, location
sentinel_asn <variable>; — geoip2 ASN source variable (e.g. $geoip2_asn)
sentinel_block_status #
syntax: sentinel_block_status 1 arg (integer); · context: http, server, location
sentinel_block_status N; default 403; 444=drop conn; bounds [400,599]
sentinel_block_ttl #
syntax: sentinel_block_ttl 1 arg (integer); · context: http, server, location
sentinel_block_ttl S; default 0 (off); >0 persists a self-ban for S sec
sentinel_c2ip_deny #
syntax: sentinel_c2ip_deny 1+ args; · context: http, server, location
CIDRs are accepted (sentinel_c2ip_deny 1.2.3.0/24 2001:db8::/32), so it is distinct from a dynamic CrowdSec ban table.
sentinel_crowdsec_default_ttl #
syntax: sentinel_crowdsec_default_ttl 1 arg (duration in seconds); · context: location
sentinel_crowdsec_default_ttl time; — TTL for expiry==0 lines
sentinel_crowdsec_feed #
syntax: sentinel_crowdsec_feed 1 arg; · context: location
sentinel_crowdsec_feed path; — location: feed file (off if unset)
sentinel_crowdsec_interval #
syntax: sentinel_crowdsec_interval 1 arg (duration in seconds); · context: location
sentinel_crowdsec_interval time; — refresh tick (default 10s)
sentinel_crowdsec_max_bytes #
syntax: sentinel_crowdsec_max_bytes 1 arg (size (k/m/g)); · context: location
sentinel_crowdsec_max_bytes size; — feed size cap
sentinel_crowdsec_stale_after #
syntax: sentinel_crowdsec_stale_after 1 arg (duration in seconds); · context: location
sentinel_crowdsec_stale_after time; — stale threshold + LRU age-out
sentinel_crowdsec_zone #
syntax: sentinel_crowdsec_zone 1 arg; · context: http
---- Phase 3: crowdsec decision-feed directives ---- */ /* sentinel_crowdsec_zone name:size; — main context (declares the shm zone)
sentinel_cs_sink_interval #
syntax: sentinel_cs_sink_interval 1 arg (duration in seconds); · context: http, server, location
sentinel_cs_sink_interval time; — drain/rewrite tick (default 10s)
sentinel_cs_sink_path #
syntax: sentinel_cs_sink_path 1 arg; · context: http, server, location
sentinel_cs_sink_path <file>; — export local bans as a CrowdSec file-acquisition decisions file (out-of-band, no network).
sentinel_cs_sink_scenario #
syntax: sentinel_cs_sink_scenario 1 arg (string); · context: http, server, location
sentinel_cs_sink_scenario <s>; — "scenario" field (default sentinel/http-abuse)
sentinel_cs_sink_ttl #
syntax: sentinel_cs_sink_ttl 1 arg (duration in seconds); · context: http, server, location
sentinel_cs_sink_ttl time; — decision duration written (default 3600s)
sentinel_datacenter_asn #
syntax: sentinel_datacenter_asn 1+ args; · context: http, server, location
sentinel_datacenter_asn N [N ...]; — flagged datacenter/abuse ASN list
sentinel_fail #
syntax: sentinel_fail 1 arg; · context: location
sentinel_fail closed flips it. - Bounded tarpit. Global concurrent-connection cap, fixed drip buffers, hard max lifetime.
sentinel_fcrdns #
syntax: sentinel_fcrdns 1 arg; · context: http, server, location
sentinel_fcrdns <zone>; — bind a location to a verdict cache + enable
sentinel_fcrdns_ttl #
syntax: sentinel_fcrdns_ttl 1 arg (duration in seconds); · context: http, server, location
sentinel_fcrdns_ttl time; — verdict cache TTL (default 3600s)
sentinel_fcrdns_verify_suffix #
syntax: sentinel_fcrdns_verify_suffix 1+ args; · context: http, server, location
sentinel_fcrdns_verify_suffix <suffix>...; — allowed PTR-name suffixes
sentinel_fcrdns_zone #
syntax: sentinel_fcrdns_zone 1 arg; · context: http
sentinel_fcrdns_zone name:size; — main context (declares the verdict cache)
sentinel_honeypot #
syntax: sentinel_honeypot 1+ args; · context: http, server, location
sentinel_honeypot <path> [path ...]; — decoy path prefix(es)
sentinel_ja3 #
syntax: sentinel_ja3 1 arg; · context: http, server, location
The ssl-fingerprint module exposes the client's JA3 MD5 as $ssl_fingerprint_ja3_hash; sentinel_ja3 scores a deny-list match at +80 (default) — above the block threshold, so a C2 client is blocked on the first request.
sentinel_ja3_deny #
syntax: sentinel_ja3_deny 1+ args; · context: http, server, location
tools/sslbl-ja3-fetch.sh downloads the feed and generates an nginx include: The generated /etc/nginx/sentinel-ja3-deny.conf is a single sentinel_ja3_deny <hash> <hash> ...; statement.
sentinel_ja4 #
syntax: sentinel_ja4 1 arg; · context: http, server, location
Run both (sentinel_ja3 + sentinel_ja4) for layered coverage. ### Blocking C2 infrastructure with the abuse.ch Feodo IP feed abuse.ch Feodo Tracker publishes a curated list of IPv4 addresses of confirmed botnet command-and-control (C2) servers (Dridex, Emotet, TrickBot, QakBot, …).
sentinel_ja4_deny #
syntax: sentinel_ja4_deny 1+ args; · context: http, server, location
sentinel_ja4_deny <ja4|hash> [...]; — denied JA4 (TLS) fingerprint list
sentinel_ja4t #
syntax: sentinel_ja4t 1 arg; · context: http, server, location
sentinel_ja4t <variable>; — PROXY-protocol TLV JA4T source (e.g. $proxy_protocol_tlv_0xe0)
sentinel_ja4t_deny #
syntax: sentinel_ja4t_deny 1+ args; · context: http, server, location
sentinel_ja4t_deny <ja4t|hash> [...]; — denied JA4T (TCP) fingerprint list
sentinel_mode #
syntax: sentinel_mode 1 arg; · context: location
In shadow mode (sentinel_mode shadow) the module scores and logs without acting.
sentinel_pow #
syntax: sentinel_pow on | off (on/off flag); · context: http, server, location
sentinel_pow on|off; serve a PoW challenge on CHALLENGE-band verdicts
sentinel_pow_difficulty #
syntax: sentinel_pow_difficulty 1 arg (integer); · context: http, server, location
sentinel_pow_difficulty N; required leading zero bits (default 16)
sentinel_pow_secret #
syntax: sentinel_pow_secret 1 arg (string); · context: http, server, location
sentinel_pow_secret <key>; HMAC signing key (required; empty = off)
sentinel_pow_ttl #
syntax: sentinel_pow_ttl 1 arg (duration in seconds); · context: http, server, location
sentinel_pow_ttl time; challenge-bucket + bypass-cookie TTL (default 3600)
sentinel_redis #
syntax: sentinel_redis 1 arg; · context: http, server, location
sentinel_redis host[:port]; — enable Redis multi-box shared ban state
sentinel_redis_interval #
syntax: sentinel_redis_interval 1 arg (duration in seconds); · context: http, server, location
sentinel_redis_interval time; — pull/flush tick (default 10s)
sentinel_redis_password #
syntax: sentinel_redis_password 1 arg (string); · context: http, server, location
sentinel_redis_password <pw>; — optional AUTH password
sentinel_redis_prefix #
syntax: sentinel_redis_prefix 1 arg (string); · context: http, server, location
sentinel_redis_prefix <ns>; — key namespace (default "sentinel")
sentinel_redis_ttl #
syntax: sentinel_redis_ttl 1 arg (duration in seconds); · context: http, server, location
sentinel_redis_ttl time; — TTL for pushed ban keys (default 3600s)
sentinel_shield #
syntax: sentinel_shield on | off (on/off flag); · context: http, server, location
sentinel_shield on|off; default off. On a TARPIT-band verdict in enforce mode, raise $sentinel_shield=1 (the operator wires it into proxy cache config to serve stale/cache-only) instead of tarpitting.
sentinel_status #
syntax: sentinel_status no args; · context: location
Zone declarations (sentinel_zone, sentinel_velocity_zone) are http-context only. --- ## Prometheus metrics sentinel_status; in a location block emits Prometheus text exposition (text/plain; version=0.0.4).
sentinel_tarpit_bytes #
syntax: sentinel_tarpit_bytes 1 arg; · context: location
sentinel_tarpit_bytes N; default 1024; bounds [1, 65536]
sentinel_tarpit_delay #
syntax: sentinel_tarpit_delay 1 arg; · context: location
sentinel_tarpit_delay ms; default 5000; bounds [100, 60000]
sentinel_tarpit_max_conns #
syntax: sentinel_tarpit_max_conns 1 arg (integer); · context: location
---- Phase 2: tarpit directives ---- */ /* sentinel_tarpit_max_conns N; default 256; 0=disabled (all TARPIT->444)
sentinel_tarpit_max_lifetime #
syntax: sentinel_tarpit_max_lifetime 1 arg; · context: location
sentinel_tarpit_max_lifetime ms; default 30000; bounds [1000, 600000]
sentinel_tarpit_maze #
syntax: sentinel_tarpit_maze on | off (on/off flag); · context: http, server, location
sentinel_tarpit_maze on|off; default off. When on, the tarpit drips HTML decoy crawl-links instead of blank padding (maze mode).
sentinel_threshold #
syntax: sentinel_threshold 1+ args; · context: location
sentinel_threshold challenge=N tarpit=M block=K; — location context
sentinel_throttle_rate #
syntax: sentinel_throttle_rate 1 arg (size (k/m/g)); · context: http, server, location
sentinel_throttle_rate size; default 0 (off, keep tarpit). On a TARPIT verdict in enforce mode, cap egress at `size` bytes/sec instead.
sentinel_velocity #
syntax: sentinel_velocity 1 arg; · context: http, server, location
sentinel_velocity <zone_name>; — bind this location to a velocity zone (opt-in)
sentinel_velocity_zone #
syntax: sentinel_velocity_zone 1+ args; · context: http
Zone declarations (sentinel_zone, sentinel_velocity_zone) are http-context only. --- ## Prometheus metrics sentinel_status; in a location block emits Prometheus text exposition (text/plain; version=0.0.4).
sentinel_weight_asn #
syntax: sentinel_weight_asn 1 arg (integer); · context: http, server, location
sentinel_weight_asn N; — added once if datacenter_asn
sentinel_weight_blocked #
syntax: sentinel_weight_blocked 1 arg (integer); · context: location
sentinel_weight_blocked N; — identity already blocked
sentinel_weight_bot #
syntax: sentinel_weight_bot 1 arg (integer); · context: location
sentinel_weight_bot N; — heuristic bot user-agent
sentinel_weight_c2ip #
syntax: sentinel_weight_c2ip 1 arg (integer); · context: http, server, location
sentinel_weight_c2ip N; — added once if c2ip_flagged
sentinel_weight_coherence #
syntax: sentinel_weight_coherence 1 arg (integer); · context: http, server, location
sentinel_weight_coherence N; — added once if ua_incoherent
sentinel_weight_crowdsec #
syntax: sentinel_weight_crowdsec 1 arg (integer); · context: location
sentinel_weight_crowdsec N; — base score weight for a crowdsec ban
sentinel_weight_errrate #
syntax: sentinel_weight_errrate 1 arg (integer); · context: location
A bot that keeps hitting 404s also accumulates error-rate score (1 point per recorded error × sentinel_weight_errrate).
sentinel_weight_header #
syntax: sentinel_weight_header 1 arg (integer); · context: location
sentinel_weight_header N; — request-header anomaly
sentinel_weight_honeypot #
syntax: sentinel_weight_honeypot 1 arg (integer); · context: location
sentinel_weight_honeypot N; — decoy-URL (honeypot) hit
sentinel_weight_ja3 #
syntax: sentinel_weight_ja3 1 arg (integer); · context: http, server, location
sentinel_weight_ja3 N; — added once if ja3_flagged
sentinel_weight_ja4 #
syntax: sentinel_weight_ja4 1 arg (integer); · context: http, server, location
sentinel_weight_ja4 N; — added once if ja4_flagged
sentinel_weight_ja4t #
syntax: sentinel_weight_ja4t 1 arg (integer); · context: http, server, location
sentinel_weight_ja4t N; — added once if ja4t_flagged
sentinel_weight_scanner #
syntax: sentinel_weight_scanner 1 arg (integer); · context: location
sentinel_weight_scanner N; — scanner-path prefix hit
sentinel_weight_velocity #
syntax: sentinel_weight_velocity 1 arg (integer); · context: location
sentinel_weight_velocity N; — added once if velocity_exceeded
sentinel_zone #
syntax: sentinel_zone 1 arg; · context: http
Zone declarations (sentinel_zone, sentinel_velocity_zone) are http-context only. --- ## Prometheus metrics sentinel_status; in a location block emits Prometheus text exposition (text/plain; version=0.0.4).
Example
http {
# ── Shared-memory zones ────────────────────────────────────────────────
# Identity store (error-rate, soft-ban, CrowdSec table)
sentinel_zone NAME:SIZE; # e.g. main:20m (required for errrate/softban)
# Velocity (request-rate) counter — separate zone
sentinel_velocity_zone NAME:SIZE
[rate=100] # max requests per window before flagged
[window=10] # sliding window, seconds
[block=3600]; # how long the flag sticks, seconds
# CrowdSec ban table (also used by Redis pull)
sentinel_crowdsec_zone NAME:SIZE; # e.g. cs:4m
# FCrDNS verdict cache
sentinel_fcrdns_zone NAME:SIZE; # e.g. fcdns:1m
# ── Core ───────────────────────────────────────────────────────────────
sentinel on|off; # default: off
sentinel_mode enforce|shadow; # default: enforce
sentinel_fail open|closed; # on zone/lookup error; default: open
sentinel_zone NAME:SIZE; # bind the errrate zone (also http context)
sentinel_threshold
challenge=30 # score ≥ this → PoW page (if enabled)
tarpit=60 # score ≥ this → tarpit / throttle / shield
block=80; # score ≥ this → 403 / 444
# ── Score weights ──────────────────────────────────────────────────────
# Set 0 to disable a signal.
sentinel_weight_errrate 1; # per error event in the sliding window
sentinel_weight_blocked 100; # identity has an active soft-ban
sentinel_weight_scanner 50; # built-in scanner-path hit
sentinel_weight_bot 30; # heuristic bot User-Agent
sentinel_weight_header 25; # malformed / anomalous request headers
sentinel_weight_honeypot 90; # operator-defined decoy path hit
sentinel_weight_velocity 30; # per-identity request rate exceeded
sentinel_weight_asn 35; # client ASN in the flagged list
sentinel_weight_c2ip 80; # client IP on C2 deny list (Feodo C2 infra)
sentinel_weight_coherence 40; # browser UA but bare HTTP client shape
sentinel_weight_ja3 80; # JA3 (TLS) fp on deny list (SSLBL malware/C2)
sentinel_weight_ja4 50; # JA4 (TLS) fp on deny list
s
…