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
…

↑ back to index