swag Crowdsec Security To Protect Your Nginx Reverse Proxy | Docker
Nginx as a reverse proxy (called swag) is easy enough to setup, as a docker container, thanks to LinuxServer.io. They even offer a plugin to integrate Crowdsec! Swag crowdsec is the plugin, but Crowdsec bouncer also needs to be installed as a separate container. You can find various not-too-difficult guides just like mine, hopefully this one is even easier?
This guide is mainly for interfacting Crowdsec with swag nginx from LinuxServer.io, but can also be applied for local/manual installations of Nginx/Wordpress/etc. You will just have to integrate the nginx bouncer plugin yourself.
Presentation
With Compose, we are going to setup a separate container for crowdsec. In a nutshell, nginx swag will call Crowdsec via an http API for every access to your websites. Crowdsec will in return decide if it’s a threat or not, and tell nginx to block the IP address or not. Calls can be done for each call or in stream mode every x seconds.

How does Crowdsec work?
This bouncer leverages nginx lua’s API, namely access_by_lua_block
to check e IP address against the local API.
Supported features:
- Live mode (query the local API for each request)
- Stream mode (pull the local API for new/old decisions every X seconds)
- Ban remediation (can ban an IP address by redirecting him or returning a custom HTML page)
- reCAPTCHA v2 remediation (can return a captcha)
- Works with IPv4/IPv6
- Support IP ranges (can apply a remediation on an IP range)
At the back, this bouncer uses crowdsec lua lib.
Quick How-To install Crowdsec Container with Docker
Step 1: Requisites and Prepare the File System
Personally, I use a central location for all the containers, let’s call it /app
mkdir -p /app/crowdsec/config mkdir -p /app/crowdsec/data alias cscli='docker exec -t crowdsec cscli'
You will need the cscli alias, believe me. Add cscli alias in your bashrc or profile.
You also need a clear separation of docker networks. Personally I use proxy, frontend and backend, plus other flavors. For this example you will need:
- Docker – I use community edition, version 20.10.12
- docker-compose – I use the latest version v2.15.1
- frontend docker network:
docker network add frontrend
- A working swag nginx instance with some proxified sites
- Swag nginx and Crowdsec to be in the same docker network 😉
Step 2: Add docker-compose.yml under /app/crowdsec
version: "3.3" # alias cscli='docker exec -t crowdsec cscli' services: crowdsec: labels: com.centurylinklabs.watchtower.enable: "true" image: docker.io/crowdsecurity/crowdsec:latest container_name: crowdsec volumes: # I like to fix the timezone and time to the host - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro - /app/crowdsec/config:/etc/crowdsec - /app/crowdsec/data:/var/lib/crowdsec/data # below are all the log mounts needed for crowdsec to analyze - /app/swag/config/log/nginx:/var/log/swag:ro - /app/mailu/data/log/front:/var/log/mailu/front:ro - /var/log:/var/log/host:ro # do not expose anything, nginx will reach out from within its internal network # expose: # - "8080" # ports: # - "8080:8080" environment: TZ: ${TZ} GID: "${GID:-1000}" PUID: 1000 PGID: 1000 # LEVEL_DEBUG: true # You can add more collections for your containers later on COLLECTIONS: crowdsecurity/nginx crowdsecurity/http-cve crowdsecurity/whitelist-good-actors CUSTOM_HOSTNAME: ${HOSTNAME} # to enroll: cscli console enroll <your online crowdsec instance>; then restart container #CROWDSEC_INSTANCE: <your online crowdsec instance> #MAPQUEST_API_KEY: <generate your own for free> restart: unless-stopped security_opt: - no-new-privileges:true networks: frontend: networks: frontend: external: true name: frontend
Then start Crowdsec for the first time:
cd /app/crowdsec docker-compose pull docker-compose up -d --build docker-compose logs --tail=25 -f
Step 3: update swag bouncer and Crowdsec acquisitions
Generate the API key for your swag nginx instance: This is something you will have to do again for each client that will access this bouncer
cscli bouncers add swag # Api key for 'swag': # c8a3775a81b21c234422c1c0ad0a1234
This is the API key you will need to add in the compose file for swag nginx. Also add the crowdsec module in the list, along with the url to crowdsec:
environment: - DOCKER_MODS=linuxserver/mods:swag-auto-reload|..more modules..|linuxserver/mods:swag-crowdsec - CROWDSEC_API_KEY=c8a3775a81b21c234422c1c0ad0a1234 - CROWDSEC_LAPI_URL=http://crowdsec:8080
Next, edit the acquisitions file: this is where you define what log will be read in which format, adapt to your needs:
vi /app/crowdsec/config/acquis.yaml
filenames: - /var/log/swag/* - /var/log/mailu/front/* labels: type: nginx --- filenames: - /var/log/host/auth.log* - /var/log/host/syslog labels: type: syslog --- filenames: - /var/log/apache2/*.log labels: type: apache2
Step 4: restart swag and Crowdsec
cd /app/crowdsec docker-compose down && docker-compose up -d cd /app/swag docker-compose down && docker-compose up -d
You can verify that the Crowdsec module has been setup properly in nginx by accessing this file:
/app/swag/config/crowdsec/crowdsec-nginx-bouncer.conf
Step 5: monitor and enjoy
To see if swag nginx connected to Crowdsec: correct result if IP Address is not null amd you can see the client’s version
cscli bouncers list
────────────────────────────────────────────────────────────────────────────────────────────────── Name IP Address Valid Last API pull Type Version Auth Type ────────────────────────────────────────────────────────────────────────────────────────────────── swag 172.20.0.13 ✔️ 2022-12-24T18:49:50Z crowdsec-nginx-bouncer v1.0.4 api-key ────────────────────────────────────────────────────────────────────────────────────────────────── ^--- check last refresh date, this proves the client is connected
Loot at the metrics and how many nefarious actors have been banned:
cscli metrics
Acquisition Metrics: ╭────────────────────────────────┬────────────┬──────────────┬────────────────┬────────────────────────╮ │ Source │ Lines read │ Lines parsed │ Lines unparsed │ Lines poured to bucket │ ├────────────────────────────────┼────────────┼──────────────┼────────────────┼────────────────────────┤ │ file:/var/log/host/auth.log │ 4 │ - │ 4 │ - │ │ file:/var/log/host/syslog │ 1.83k │ - │ 1.83k │ - │ │ file:/var/log/nginx/access.log │ 3 │ 3 │ - │ - │ │ file:/var/log/swag/access.log │ 68 │ 68 │ - │ 31 │ ╰────────────────────────────────┴────────────┴──────────────┴────────────────┴────────────────────────╯ Bucket Metrics: ╭──────────────────────────────────────┬───────────────┬───────────┬──────────────┬────────┬─────────╮ │ Bucket │ Current Count │ Overflows │ Instantiated │ Poured │ Expired │ ├──────────────────────────────────────┼───────────────┼───────────┼──────────────┼────────┼─────────┤ │ crowdsecurity/http-bad-user-agent │ - │ - │ 1 │ 1 │ 1 │ │ crowdsecurity/http-crawl-non_statics │ - │ - │ 19 │ 29 │ 19 │ │ crowdsecurity/http-probing │ - │ - │ 1 │ 1 │ 1 │ ╰──────────────────────────────────────┴───────────────┴───────────┴──────────────┴────────┴─────────╯ Parser Metrics: ╭─────────────────────────────────┬───────┬────────┬──────────╮ │ Parsers │ Hits │ Parsed │ Unparsed │ ├─────────────────────────────────┼───────┼────────┼──────────┤ │ child-crowdsecurity/http-logs │ 213 │ 150 │ 63 │ │ child-crowdsecurity/nginx-logs │ 71 │ 71 │ - │ │ child-crowdsecurity/syslog-logs │ 1.84k │ 1.84k │ - │ │ crowdsecurity/dateparse-enrich │ 71 │ 71 │ - │ │ crowdsecurity/geoip-enrich │ 71 │ 71 │ - │ │ crowdsecurity/http-logs │ 71 │ 68 │ 3 │ │ crowdsecurity/nginx-logs │ 71 │ 71 │ - │ │ crowdsecurity/non-syslog │ 71 │ 71 │ - │ │ crowdsecurity/syslog-logs │ 1.84k │ 1.84k │ - │ │ crowdsecurity/whitelists │ 71 │ 71 │ - │ ╰─────────────────────────────────┴───────┴────────┴──────────╯ Local Api Metrics: ╭────────────────────┬────────┬──────╮ │ Route │ Method │ Hits │ ├────────────────────┼────────┼──────┤ │ /v1/alerts │ GET │ 1 │ │ /v1/heartbeat │ GET │ 24 │ │ /v1/watchers/login │ POST │ 3 │ ╰────────────────────┴────────┴──────╯ Local Api Machines Metrics: ╭──────────┬───────────────┬────────┬──────╮ │ Machine │ Route │ Method │ Hits │ ├──────────┼───────────────┼────────┼──────┤ │ crowdsec │ /v1/alerts │ GET │ 1 │ │ crowdsec │ /v1/heartbeat │ GET │ 24 │ ╰──────────┴───────────────┴────────┴──────╯
Look at the latest decisions made:
cscli decisions list
╭───────┬──────────┬───────────────────┬──────────────────────────┬────────┬─────────┬─────────────────────────┬────────┬────────────────────┬──────────╮ │ ID │ Source │ Scope:Value │ Reason │ Action │ Country │ AS │ Events │ expiration │ Alert ID │ ├───────┼──────────┼───────────────────┼──────────────────────────┼────────┼─────────┼─────────────────────────┼────────┼────────────────────┼──────────┤ │ 15006 │ crowdsec │ Ip:184.98.47.230 │/nginx-req-limit-exceeded │ ban │ US │ 209 CENTURYLINK-US-LEGAC│ 6 │ 3h58m33.694712793s │ 7 │ │ 15005 │ crowdsec │ Ip:35.235.116.5 │/http-bad-user-agent │ ban │ US │ 396982 GOOGLE-CLOUD-PLAT│ 2 │ 3h53m38.172330115s │ 6 │ │ 15004 │ crowdsec │ Ip:51.142.82.33 │/http-bad-user-agent │ ban │ GB │ 8075 MICROSOFT-CORP-MSN-│ 2 │ 3h44m28.590167436s │ 5 │ │ 3 │ crowdsec │ Ip:167.94.138.44 │/http-bad-user-agent │ ban │ US │ 398324 CENSYS-ARIN-01 │ 2 │ 2h33m42.390288213s │ 3 │ ╰───────┴──────────┴───────────────────┴──────────────────────────┴────────┴─────────┴─────────────────────────┴────────┴────────────────────┴──────────╯
Step 6: test it!
Test it by banning yourself: take note of your IP address and ban yourself:
cscli decisions add --ip 1.2.3.4 --type ban --duration 10m
Now refresh one of your sites and see for yourself!

Finally, unban yourself:
cscli decisions delete --ip 1.2.3.4 cscli alerts delete --ip 1.2.3.4
You are all set, and protected!
Optional steps to go further
In a future post, I will show you how to easily:
- Increase the ban time exponentially with every further “attacks”
- Test it further with Nikto basic exploit tests
- Notify alerts to your own discord server
- Pair the notification with a MAPQUEST_API_KEY so you even have a map tile with the alert
- Pair it with Google reCaptcha v2 to offer a Captha instead of a ban
- Enroll it with the free Crowdsec dashboard for improved visualization of attacks
- Change the ban message to something more NSFW