← Start of tutorial

WIUNIX ACADEMY

CrowdSec and Nginx: trustworthy logs, private LAPI and reversible IP bans

Page 3 of 5. Review the earlier steps and prerequisites before continuing.

YOUR SYSTEM. YOUR STEPS.

Where are you working?

Choose your OS and version. The prerequisites, commands and troubleshooting follow your environment.

Debian12Reviewed against official documentation ·

USE YOUR OWN VALUES

Configure this tutorial

Set the example values for your environment; related explanations and copy panels update together. Never enter passwords, private keys or tokens. Values stay in this tab and are not sent to the server or share links. Check actual DNS, certificates and network policy before running commands.

Different IP addresses, domains or ports? Open the settings below, change the values and select “Apply to tutorial”, then copy the updated commands.

Set values for my environment7 editable values
06

LET’S BUILD

Install and inspect the Nginx collection

The Hub index and collection fetch public detection content; they do not enroll an engine in Console or register with CAPI. Inspect parsers and scenarios and record the installed revisions. The nginx label must match the conventional combined access-log grammar configured below. Acquisition merely reads lines; parsers normalize them, scenarios detect patterns, profiles turn eligible alerts into decisions.

The raw archive has no pre-enabled Linux base collection. Explicit syslog-logs supplies the non-syslog input stage and dateparse-enrich supplies timestamps; install both before the Nginx collection. Ignore generic systemctl reload suggestions printed by Hub commands for this private foreground lab.

bash

Dedicated disposable VM · terminal

set -eu
sudo '/srv/academy-crowdsec/crowdsec-v1.8.1/cmd/crowdsec-cli/cscli' -c '/srv/academy-crowdsec/config/engine.yaml' hub update
sudo '/srv/academy-crowdsec/crowdsec-v1.8.1/cmd/crowdsec-cli/cscli' -c '/srv/academy-crowdsec/config/engine.yaml' parsers install crowdsecurity/syslog-logs crowdsecurity/dateparse-enrich
sudo '/srv/academy-crowdsec/crowdsec-v1.8.1/cmd/crowdsec-cli/cscli' -c '/srv/academy-crowdsec/config/engine.yaml' collections install crowdsecurity/nginx
sudo '/srv/academy-crowdsec/crowdsec-v1.8.1/cmd/crowdsec-cli/cscli' -c '/srv/academy-crowdsec/config/engine.yaml' collections inspect crowdsecurity/nginx --no-metrics
How do I know it worked?

The Nginx collection and dependencies are installed and inspect lists actual versions.

07

LET’S BUILD

Register without printing or passing keys in argv

The Python process captures the generated key in memory and writes a new0600 private file; do not enable shell tracing, print the file, paste it into this article or commit it. No key is supplied on a command line. Machine auto-registration writes its password into the new private credential path. If any stage fails, inspect only non-secret errors; do not overwrite the partial private files. Use a fresh lab or revoke the dedicated identity before a reviewed retry.

bash

Dedicated disposable VM · terminal

sudo python3 - <<'PY'
import os,subprocess
from pathlib import Path
r=Path('/srv/academy-crowdsec')
cli=[str(r/'crowdsec-v1.8.1/cmd/crowdsec-cli/cscli'),'-c',str(r/'config/engine.yaml')]
creds=r/'config/local-api.yaml'
assert not creds.exists() and not creds.is_symlink(), 'Credentials already present'
subprocess.run(cli+['machines','add','academy-agent','--auto','--file',str(creds),'--url','http://127.0.0.1:18081'],check=True,stdout=subprocess.DEVNULL)
os.chmod(creds,0o600)
p=r/'config/bouncer.conf'
fd=os.open(p,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600)
try:
    key=subprocess.check_output(cli+['bouncers','add','academy_nginx','-o','raw'],text=True).strip()
    assert key and '\n' not in key
    text='API_URL=http://127.0.0.1:18081\nAPI_KEY='+key+'\nMODE=live\nCACHE_EXPIRATION=1\nREQUEST_TIMEOUT=500\nBOUNCING_ON_TYPE=ban\nFALLBACK_REMEDIATION=ban\nRET_CODE=403\nAPPSEC_URL=\n'
    with os.fdopen(fd,'w',encoding='utf-8') as f: f.write(text)
finally:
    key=None
print('Private machine credentials and exclusive bouncer file created; no key displayed')
PY
How do I know it worked?

Only a non-secret confirmation appears; bouncer.conf and local-api.yaml are0600.

08

LET’S BUILD

Configure a separate Nginx process

Edit the reserved configuration with the following file. No real-IP module directives are enabled: the TCP peer supplies both log identity and bouncer identity. Forwarded headers are ignored. The access-phase Lua hook must be reached; a rewrite-phase return200 would bypass it, so this lab serves a real static file through try_files.

bash

Dedicated disposable VM · terminal

sudo nano '/srv/academy-crowdsec/config/nginx.conf'
How do I know it worked?

Only the dedicated configuration is edited; no global conf.d include is added.

Nginx Lua access hook and conventional logs

File content. The distro module include loads the matching NDK/Lua modules. The private key is read by the root master at initialization; workers run as www-data and need no access to the private config directory.

nginx · /srv/academy-crowdsec/config/nginx.conf
load_module /usr/lib/nginx/modules/ndk_http_module.so;
load_module /usr/lib/nginx/modules/ngx_http_lua_module.so;
user www-data;
worker_processes 1;
pid /srv/academy-crowdsec/run/nginx.pid;
error_log /srv/academy-crowdsec/logs/nginx-error.log warn;
events { worker_connections 128; }
http {
    lua_package_path '/srv/academy-crowdsec/crowdsec-nginx-bouncer-v1.2.3/lua-mod/lib/?.lua;/srv/academy-crowdsec/rocks/share/lua/5.1/?.lua;;';
    lua_shared_dict crowdsec_cache 10m;
    init_by_lua_block {
        cs = require "crowdsec"
        local ok, err = cs.init("/srv/academy-crowdsec/config/bouncer.conf", "crowdsec-nginx-bouncer/v1.2.3")
        if ok == nil then error(err) end
    }
    log_format academy '$remote_addr - $remote_user [$time_local] '
                       '"$request" $status $body_bytes_sent '
                       '"$http_referer" "$http_user_agent"';
    access_log /srv/academy-crowdsec/logs/access.log academy;
    server {
        listen 127.0.0.1:18080;
        server_name localhost;
        root /srv/academy-crowdsec/www;
        location / {
            access_by_lua_block { cs.Allow(ngx.var.remote_addr) }
            try_files $uri =404;
        }
    }
}
09

LET’S BUILD

Validate and start the foreground engine

Use terminalA. The last command stays running; leave it open. Validation checks syntax, not the complete detection/enforcement chain. No online credentials exist, and the engine is explicitly started without CAPI.

bash

Dedicated disposable VM · terminal

sudo '/srv/academy-crowdsec/crowdsec-v1.8.1/cmd/crowdsec/crowdsec' -c '/srv/academy-crowdsec/config/engine.yaml' -t -no-capi
sudo nginx -t -p '/srv/academy-crowdsec/' -c '/srv/academy-crowdsec/config/nginx.conf'
sudo '/srv/academy-crowdsec/crowdsec-v1.8.1/cmd/crowdsec/crowdsec' -c '/srv/academy-crowdsec/config/engine.yaml' -no-capi
How do I know it worked?

Both configuration tests succeed; terminalA runs the local engine and API.

10

LET’S BUILD

Allow trusted management, then start the lab web server

In terminalB create the named management allowlist before enforcement testing. The allowlist skips future matching decisions; review existing decisions separately rather than assuming they disappeared. Do not allowlist entire customer NATs. The test source 127.0.0.2 is deliberately outside 127.0.0.1/32.

bash

Dedicated disposable VM · terminal

sudo '/srv/academy-crowdsec/crowdsec-v1.8.1/cmd/crowdsec-cli/cscli' -c '/srv/academy-crowdsec/config/engine.yaml' allowlists create academy-management -d 'Trusted lab management only'
sudo '/srv/academy-crowdsec/crowdsec-v1.8.1/cmd/crowdsec-cli/cscli' -c '/srv/academy-crowdsec/config/engine.yaml' allowlists add academy-management '127.0.0.1/32'
sudo '/srv/academy-crowdsec/crowdsec-v1.8.1/cmd/crowdsec-cli/cscli' -c '/srv/academy-crowdsec/config/engine.yaml' allowlists inspect academy-management
sudo nginx -p '/srv/academy-crowdsec/' -c '/srv/academy-crowdsec/config/nginx.conf'
curl --noproxy '*' --interface '127.0.0.2' --fail http://127.0.0.1:18080/index.html
How do I know it worked?

Allowlist contains the reviewed CIDR; local HTTP returns academy-ok.

Explore all tutorials ↗