Installing CrowdSec with Nginx Proxy Manager (NPMPlus)

Installing CrowdSec with Nginx Proxy Manager (NPMPlus)

In this guide, we will set up CrowdSec with Nginx Proxy Manager Plus (NPMPlus) using Docker-Compose. CrowdSec is a powerful, open-source intrusion prevention system that leverages collaborative security to detect and mitigate threats. NPMPlus is an enhanced version of Nginx Proxy Manager, a user-friendly reverse proxy tool for managing web services. By integrating CrowdSec with NPMPlus, you can enhance the security of your network by automatically blocking malicious traffic while maintaining seamless management of your web applications.

Why Combine CrowdSec and NPMPlus?

  1. Enhanced Security: CrowdSec protects your services by analyzing traffic patterns and blocking suspicious activity based on its extensive community-driven database.
  2. Ease of Use: NPMPlus simplifies the management of reverse proxy configurations, SSL certificates, and domains.
  3. Proactive Defense: The integration enables real-time protection against bots, brute force attacks, and other threats, ensuring your network remains secure and resilient.
Note: We will be using a fork of Nginx Proxy Manager, which called NPMplus. Thanks to its developer ZoeyVid

Prerequisites

  1. A server with Docker and Docker-Compose installed.
  2. Basic knowledge of Docker and YAML configuration.
  3. Access to the NPMPlus Docker image (requires a valid subscription).

Step 1: Prepare Your Environment

Create a Project Directory

mkdir crowdsec-npmplus
cd crowdsec-npmplus

Create a docker-compose.yml File

Use the following configuration to define the services for CrowdSec and NPMPlus:

Note : Here I am using mariadb as database, you can always remove the same if you want to use sqllite3. And geoipupdate is an optional, but recommended
services:
  npmplus:
    container_name: npmplus
    image: zoeyvid/npmplus
    restart: always
    ports:
      - "81:81"
      - "80:80"
      - "443:443"
    volumes:
      - "./npm:/data"
      - "./www:/var/www"
    environment:
      - "TZ=Asia/Kolkata"           #! Change Me
      - "[email protected]"  #! Change Me
      - "ACME_SERVER=https://api.buypass.com/acme/directory"   
      - "ACME_MUST_STAPLE=false"
      - "ACME_OCSP_STAPLING=false"
      - "ACME_KEY_TYPE=rsa"
      - "NPM_PORT=81"
      - "GOA_PORT=91"
      - "GOAIWSP=48692"
      - "DB_MYSQL_HOST=mariadb"
      - "DB_MYSQL_PORT=3306"
      - "DB_MYSQL_USER=nginxadmin"
      - "DB_MYSQL_PASSWORD=strong password" #! Change Me
      - "DB_MYSQL_NAME=nginxdb"
      - "LOGROTATE=true"
      - "LOGROTATIONS=7"
      - "X_FRAME_OPTIONS=sameorigin"
      - "GOA=true"
      - "GOACLA=--agent-list --real-os --double-decode --anonymize-ip --anonymize-level=2 --keep-last=7 --with-output-resolver --no-query-string"
    depends_on:
      - mariadb
    networks:
      server-farm:
        ipv4_address: 172.50.50.12

  mariadb:
    image: 'mariadb:10.11.5'
    restart: unless-stopped
    environment:
      - "MYSQL_ROOT_PASSWORD=strong password" #! Change Me
      - "MYSQL_DATABASE=nginxdb"
      - "MYSQL_USER=nginxadmin"
      - "MYSQL_PASSWORD=strong password"  #! Change Me
    volumes:
      - ./mysql:/var/lib/mysql
    networks:
      server-farm:
        ipv4_address: 172.50.50.10

  phpmyadmin:
    image: lscr.io/linuxserver/phpmyadmin
    container_name: phpmyadmin
    ports:
      - 8445:80
    environment:
      - PMA_ARBITRARY=1
      - PMA_HOST=mariadb
      - PUID=1001
      - PGID=1001
      - TZ=Asia/Kolkata
    volumes:
      - ./phpadmin/config:/config
    depends_on:
      - mariadb
    networks:
      server-farm:
        ipv4_address: 172.50.50.11

  crowdsec:
    container_name: crowdsec
    image: crowdsecurity/crowdsec
    restart: always
    ports:
      - "7422:7422"
      - "8080:8080"
    environment:
      - "TZ=Asia/Kolkata"
      - "COLLECTIONS=ZoeyVid/npmplus"
    volumes:
      - "./npm/crowdsec/conf:/etc/crowdsec"
      - "./npm/crowdsec/data:/var/lib/crowdsec/data"
      - "./npm/nginx:/opt/npm/nginx:ro"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
    network_mode: host    #! Make sure this is set to "host" in order to work

  geoipupdate:
    container_name: npmplus-geoipupdate
    image: maxmindinc/geoipupdate
    restart: always
    environment:
      - "TZ=Asia/Kolkata"
      - "GEOIPUPDATE_EDITION_IDS=GeoLite2-Country GeoLite2-City GeoLite2-ASN"
      - "GEOIPUPDATE_ACCOUNT_ID=Your_account_Key"   #! Change Me
      - "GEOIPUPDATE_LICENSE_KEY=your_license_key"  #! Change Me
      - "GEOIPUPDATE_FREQUENCY=24"
    volumes:
      - "./npm/goaccess/geoip:/usr/share/GeoIP"
    networks:
      server-farm:
        ipv4_address: 172.50.50.14

networks:
  server-farm:
    driver: bridge
    ipam:
        config:
        - subnet: 172.50.50.0/24

bring the stack by executing docker compose up command.

Log in to the Admin UI When your docker container is running, connect to it on port 81 for the admin interface. Sometimes this can take a little bit because of the entropy of keys. You may need to open port 81 in your firewall. You may need to use another IP-Address. https://127.0.0.1:81 Default Admin User

Email:    [email protected]
Password: iArhP1j7p1P6TA92FA2FMbbUGYqwcYzxC4AVEe12Wbi94FY9gNN62aKyF1shrvG4NycjjX9KfmDQiwkLZH1ZDR9xMjiG2QmoHXi

Step 2: CrowdSec configuration

  1. Open and modify npm/crowdsec/conf/acquis.d/npmplus.yaml (location may change based on your installation)
filenames:
  - /opt/npm/nginx/access.log
labels:
  type: npmplus
---
source: docker
container_name:
 - npmplus
labels:
  type: npmplus
---
source: docker
container_name:
 - npmplus
labels:
  type: modsecurity
---
listen_addr: 0.0.0.0:7422
appsec_config: crowdsecurity/appsec-default
name: appsec
source: appsec
labels:
  type: appsec

Step 3: Generate an API Key for the Bouncer

Once the CrowdSec container is running, generate an API key for the Nginx bouncer:

docker exec -it crowdsec cscli bouncers add nginx-bouncer

 Save the generated api_key

Open and Modify npm/crowdsec/crowdsec.conf file

  • set ENABLED to true
  • set API_KEY to the generated value.

the file will be look like this

ENABLED=true      #!Set to True
API_URL=http://localhost:8080
API_KEY=Your_API_Key    #!Change to your API Key
CACHE_EXPIRATION=1
# bounce for all type of remediation that the bouncer can receive from the local API
BOUNCING_ON_TYPE=all
FALLBACK_REMEDIATION=ban
REQUEST_TIMEOUT=2500
UPDATE_FREQUENCY=10
# By default internal requests are ignored, such as any path affected by rewrite rule.
# set ENABLE_INTERNAL=true to allow checking on these internal requests.
ENABLE_INTERNAL=false
# live or stream
MODE=live
# exclude the bouncing on those location
EXCLUDE_LOCATION=
#those apply for "ban" action
# /!\ REDIRECT_LOCATION and RET_CODE can't be used together. REDIRECT_LOCATION take priority over RET_CODE
BAN_TEMPLATE_PATH=/data/crowdsec/ban.html
REDIRECT_LOCATION=
RET_CODE=
#those apply for "captcha" action
#valid providers are recaptcha, hcaptcha, turnstile
CAPTCHA_PROVIDER=
# Captcha Secret Key
SECRET_KEY=
# Captcha Site key
SITE_KEY=
CAPTCHA_TEMPLATE_PATH=/data/crowdsec/captcha.html
CAPTCHA_EXPIRATION=3600
APPSEC_URL=http://10.30.30.204:7422
APPSEC_FAILURE_ACTION=passthrough
APPSEC_CONNECT_TIMEOUT=1000
APPSEC_SEND_TIMEOUT=30000
APPSEC_PROCESS_TIMEOUT=10000
ALWAYS_SEND_TO_APPSEC=false
SSL_VERIFY=true

Once all done, restart the docker stack by executing docker compose restart

Step 4: Verify Integration

To confirm CrowdSec is protecting your services:

  1. Add your external IP address (connect to any free vpn service) to crowdsec block list
docker exec -it crowdsec cscli decisions add -i 123.456.789.123
  1. If everything works, you will see crowdsec block page.

Conclusion

By combining the security features of CrowdSec with the user-friendly interface of NPMPlus, you can create a robust and secure reverse proxy setup. This integration provides proactive defense against threats while simplifying service management, making it an essential addition to any modern network environment.

Side Note : Those who using Immich photo backup solution, Please go through this link and make necessary changes #1241

let me know if you have any questions.