Tools: Latest: Setting Up CrowdSec on your Linux Server: A Complete Guide

Tools: Latest: Setting Up CrowdSec on your Linux Server: A Complete Guide

Prerequisites

1. Installing CrowdSec

2. Enrolling into the CrowdSec Security Engine

3. Installing the Firewall Bouncer

4. Installing Collections

5. Configuring Log Acquisition

6. Whitelisting IPs with cscli allowlist

7. Configuring nftables as the Bouncer Backend

8. Prometheus + Grafana Integration

Enable Prometheus in CrowdSec

Configure Prometheus to Scrape CrowdSec

Import the Grafana Dashboard

9. Telegram Notification Integration

Step 1: Create a Telegram Bot

Step 2: Configure the Plugin

Step 3: Add Notifications in profiles.yaml

Step 4: Reload and Test

Verifying the Full Stack

Conclusion Previously, I wrote about setting up fail2ban to protect your server from malicious attacks and brute-force attempts, which works quite well. However, there is a better alternative: CrowdSec. CrowdSec is an open-source, collaborative security engine that detects and blocks malicious behavior using shared threat intelligence. Unlike traditional fail2ban-style tools, it crowdsources attack patterns across its entire user base, which means your server benefits from bans triggered by attacks on other machines worldwide.

This guide walks through a full production setup on the Ubuntu 22.04 LTS operating system, which should also work on other systems. CrowdSec provides an official repository script that handles APT source registration: Verify the service is running: Note: On first install, CrowdSec runs a detection wizard (wizard.sh) that scans your running services and pre-populates the log acquisition config. You can find the log acquisition config from this location and configure it as per your use case /etc/crowdsec/acquis.yaml Enrollment links your local agent to the CrowdSec Console, giving you access to the shared blocklist network, centralized alert management and remote decision control. Once accepted, your instance appears under Engines with a green status. You can now install their blocklists from the UI and manage decisions remotely. Now that you have your security engine running, it's time to install the remediation component. The remediation component is primarily responsible for blocking threat actors. One great thing about CrowdSec is that you can choose which type of remediation component you want. For example, if you want to ban an IP from your firewall, you can use cs-firewall-bouncer. If you want to restrict the IP from your Nginx server, then you will install cs-nginx-bouncer, and there are many more options. Here, we will use the nftables-compatible cs-firewall-bouncer. Confirm the bouncer registered with the agent: Warning: Ensure the nf_tables kernel module is loaded before starting the bouncer: sudo modprobe nf_tables Collections bundle parsers and detection scenarios for specific services. Install the ones matching your environment: Activate the configuration change: Verify what's installed: The output should show something like below: Tip: There's a lot of already developed collections by their community out there. You can search the available collections on their console app and then install it with the mentioned command. CrowdSec reads logs via /etc/crowdsec/acquis.yaml. While the wizard generates this automatically, you should review and modify it based on your specific use cases. For example, if your Nginx logs are stored in a custom location rather than /var/log/nginx/error.log, you must update the configuration accordingly. Below is a sample illustrating the structure of an acquis.yaml file: After any changes don't forget to reload crowdsec: Use sudo cscli metrics to confirm log files are being tailed. Zero parsed lines typically means a missing collection or wrong type label. CrowdSec newer versions include a native allowlist command to permanently exempt IPs or CIDRs from bans. It is useful for your own infrastructure, monitoring tools or trusted partners. My recommendation is always whitelist your own IPs so that you don't get accidentally banned and blocked out of your own server. Allowlisted IPs are evaluated before any decision is applied and they will never be banned even if they trigger a scenario. Edit the bouncer config at /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml: The bouncer API key is auto-generated on install. To retrieve or regenerate it: Restart the bouncer and verify nftables rules: Warning: If you're running both iptables and nftables, use the iptables-nft backend to avoid rule conflicts: sudo update-alternatives --set iptables /usr/sbin/iptables-nft CrowdSec exposes a Prometheus metrics endpoint out of the box — no extra plugin needed. Confirm or add the following in /etc/crowdsec/config.yaml: Add a scrape job to your prometheus.yml: Key metrics to monitor: CrowdSec's HTTP notification plugin can POST alerts to any webhook, including Telegram's Bot API. Alerts include inline buttons linking the offending IP to Shodan and the CrowdSec CTI. Group chat IDs are negative integers (e.g., -123456789). Create /etc/crowdsec/notifications/http_tg.yaml: Edit /etc/crowdsec/profiles.yaml to attach http_tg to your remediation profiles: If the test fails, check /var/log/crowdsec/crowdsec.log for plugin errors. Common issues: wrong chat ID format, bot not added to the group, or malformed JSON in the format template. If everything is alright the alert notifications will be like as below !Crowdsec telegram notification example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ne0av5eesel0xqe4j5o8.png) Once everything is configured, run these commands to confirm each layer is working: You now have a fully operational CrowdSec stack: the agent parsing logs from nginx, SSH, MySQL, and syslog, the nftables bouncer enforcing bans at the kernel level, IP allowlists protecting trusted sources, Prometheus and Grafana providing observability and Telegram delivering real-time alerts with one-click threat investigation links. Because CrowdSec is collaborative, every ban your instance issues contributes back to the shared blocklist making the network stronger for everyone. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command
nftables nftables: ipv4: enabled: true set-only: false table: crowdsec chain: crowdsec-chain priority: -10 ipv6: enabled: true set-only: false table: crowdsec6 chain: crowdsec6-chain priority: -10 nftables_hooks: - input - forward # packet filter pf: # an empty string disables the anchor anchor_name: "" mode: nftables update_frequency: 10s log_mode: file log_dir: /var/log/ log_level: info log_compression: true log_max_size: 100 log_max_backups: 3 log_max_age: 30 api_url: http://127.0.0.1:8080/ api_key: insecure_skip_verify: false disable_ipv6: false deny_action: DROP deny_log: false supported_decisions_types: - ban blacklists_ipv4: crowdsec-blacklists blacklists_ipv6: crowdsec6-blacklists ipset_type: nethash

nftables nftables: ipv4: enabled: true set-only: false table: crowdsec chain: crowdsec-chain priority: -10 ipv6: enabled: true set-only: false table: crowdsec6 chain: crowdsec6-chain priority: -10 nftables_hooks: - input - forward # packet filter pf: # an empty string disables the anchor anchor_name: "" mode: nftables update_frequency: 10s log_mode: file log_dir: /var/log/ log_level: info log_compression: true log_max_size: 100 log_max_backups: 3 log_max_age: 30 api_url: http://127.0.0.1:8080/ api_key: insecure_skip_verify: false disable_ipv6: false deny_action: DROP deny_log: false supported_decisions_types: - ban blacklists_ipv4: crowdsec-blacklists blacklists_ipv6: crowdsec6-blacklists ipset_type: nethash

nftables nftables: ipv4: enabled: true set-only: false table: crowdsec chain: crowdsec-chain priority: -10 ipv6: enabled: true set-only: false table: crowdsec6 chain: crowdsec6-chain priority: -10 nftables_hooks: - input - forward # packet filter pf: # an empty string disables the anchor anchor_name: "" sudo cscli bouncers list # Regenerate if needed sudo cscli bouncers delete crowdsec-firewall-bouncer sudo cscli bouncers add crowdsec-firewall-bouncer sudo cscli bouncers list # Regenerate if needed sudo cscli bouncers delete crowdsec-firewall-bouncer sudo cscli bouncers add crowdsec-firewall-bouncer sudo cscli bouncers list # Regenerate if needed sudo cscli bouncers delete crowdsec-firewall-bouncer sudo cscli bouncers add crowdsec-firewall-bouncer sudo systemctl restart crowdsec-firewall-bouncer sudo nft list ruleset | grep crowdsec sudo systemctl restart crowdsec-firewall-bouncer sudo nft list ruleset | grep crowdsec sudo systemctl restart crowdsec-firewall-bouncer sudo nft list ruleset | grep crowdsec prometheus: enabled: true level: full listen_addr: 127.0.0.1 listen_port: 6060 prometheus: enabled: true level: full listen_addr: 127.0.0.1 listen_port: 6060 prometheus: enabled: true level: full listen_addr: 127.0.0.1 listen_port: 6060 sudo systemctl reload crowdsec curl -s http://127.0.0.1:6060/metrics | head -20 sudo systemctl reload crowdsec curl -s http://127.0.0.1:6060/metrics | head -20 sudo systemctl reload crowdsec curl -s http://127.0.0.1:6060/metrics | head -20 scrape_configs: - job_name: 'crowdsec' static_configs: - targets: ['localhost:6060'] scrape_interval: 30s scrape_configs: - job_name: 'crowdsec' static_configs: - targets: ['localhost:6060'] scrape_interval: 30s scrape_configs: - job_name: 'crowdsec' static_configs: - targets: ['localhost:6060'] scrape_interval: 30s sudo systemctl reload prometheus # Or via the HTTP API if enabled: curl -X POST http://localhost:9090/-/reload sudo systemctl reload prometheus # Or via the HTTP API if enabled: curl -X POST http://localhost:9090/-/reload sudo systemctl reload prometheus # Or via the HTTP API if enabled: curl -X POST http://localhost:9090/-/reload curl -s https://api.telegram.org/bot/getUpdates \ | python3 -m json.tool | grep '"id"' curl -s https://api.telegram.org/bot/getUpdates \ | python3 -m json.tool | grep '"id"' curl -s https://api.telegram.org/bot/getUpdates \ | python3 -m json.tool | grep '"id"' # Author: Nirjas Jakilim type: http name: http_tg log_level: info format: | { "chat_id": "-YOUR_CHAT_ID", "text": " {{range . -}} {{$alert := . -}} {{range .Decisions -}} {{if $alert.Source.Cn -}} Scenario: {{.Scenario}} IP: {{.Value}} Ban Duration: {{.Duration}} Country: {{$alert.Source.Cn}} AS Name: {{$alert.Source.AsName}} {{end}} {{if not $alert.Source.Cn -}} Scenario: {{.Scenario}} IP: {{.Value}} Ban Duration: {{.Duration}} Country: Unknown {{end}} {{end -}} {{end -}} ", "reply_markup": { "inline_keyboard": [ {{ $arrLength := len . -}} {{ range $i, $value := . -}} {{ $V := $value.Source.Value -}} [ { "text": "See {{ $V }} on shodan.io", "url": "https://www.shodan.io/host/{{ $V }}" }, { "text": "See {{ $V }} on crowdsec.net", "url": "https://app.crowdsec.net/cti/{{ $V }}" } ]{{if lt $i ( sub $arrLength 1) }},{{end }} {{end -}} ] } } url: https://api.telegram.org/bot/sendMessage method: POST headers: Content-Type: "application/json" # Author: Nirjas Jakilim type: http name: http_tg log_level: info format: | { "chat_id": "-YOUR_CHAT_ID", "text": " {{range . -}} {{$alert := . -}} {{range .Decisions -}} {{if $alert.Source.Cn -}} Scenario: {{.Scenario}} IP: {{.Value}} Ban Duration: {{.Duration}} Country: {{$alert.Source.Cn}} AS Name: {{$alert.Source.AsName}} {{end}} {{if not $alert.Source.Cn -}} Scenario: {{.Scenario}} IP: {{.Value}} Ban Duration: {{.Duration}} Country: Unknown {{end}} {{end -}} {{end -}} ", "reply_markup": { "inline_keyboard": [ {{ $arrLength := len . -}} {{ range $i, $value := . -}} {{ $V := $value.Source.Value -}} [ { "text": "See {{ $V }} on shodan.io", "url": "https://www.shodan.io/host/{{ $V }}" }, { "text": "See {{ $V }} on crowdsec.net", "url": "https://app.crowdsec.net/cti/{{ $V }}" } ]{{if lt $i ( sub $arrLength 1) }},{{end }} {{end -}} ] } } url: https://api.telegram.org/bot/sendMessage method: POST headers: Content-Type: "application/json" # Author: Nirjas Jakilim type: http name: http_tg log_level: info format: | { "chat_id": "-YOUR_CHAT_ID", "text": " {{range . -}} {{$alert := . -}} {{range .Decisions -}} {{if $alert.Source.Cn -}} Scenario: {{.Scenario}} IP: {{.Value}} Ban Duration: {{.Duration}} Country: {{$alert.Source.Cn}} AS Name: {{$alert.Source.AsName}} {{end}} {{if not $alert.Source.Cn -}} Scenario: {{.Scenario}} IP: {{.Value}} Ban Duration: {{.Duration}} Country: Unknown {{end}} {{end -}} {{end -}} ", "reply_markup": { "inline_keyboard": [ {{ $arrLength := len . -}} {{ range $i, $value := . -}} {{ $V := $value.Source.Value -}} [ { "text": "See {{ $V }} on shodan.io", "url": "https://www.shodan.io/host/{{ $V }}" }, { "text": "See {{ $V }} on crowdsec.net", "url": "https://app.crowdsec.net/cti/{{ $V }}" } ]{{if lt $i ( sub $arrLength 1) }},{{end }} {{end -}} ] } } url: https://api.telegram.org/bot/sendMessage method: POST headers: Content-Type: "application/json" # Skip re-banning IPs that already have an active decision name: silence_ip_remediation filters: - Alert.Remediation == true && Alert.GetScope() == "Ip" && GetActiveDecisionsCount(Alert.GetValue()) > 0 on_success: break --- # Default IP ban — 500h with Telegram notification name: default_ip_remediation filters: - Alert.Remediation == true && Alert.GetScope() == "Ip" decisions: - type: ban duration: 500h notifications: - http_tg on_success: break --- # Range ban — 500h with Telegram notification name: default_range_remediation filters: - Alert.Remediation == true && Alert.GetScope() == "Range" decisions: - type: ban duration: 500h notifications: - http_tg on_success: break # Skip re-banning IPs that already have an active decision name: silence_ip_remediation filters: - Alert.Remediation == true && Alert.GetScope() == "Ip" && GetActiveDecisionsCount(Alert.GetValue()) > 0 on_success: break --- # Default IP ban — 500h with Telegram notification name: default_ip_remediation filters: - Alert.Remediation == true && Alert.GetScope() == "Ip" decisions: - type: ban duration: 500h notifications: - http_tg on_success: break --- # Range ban — 500h with Telegram notification name: default_range_remediation filters: - Alert.Remediation == true && Alert.GetScope() == "Range" decisions: - type: ban duration: 500h notifications: - http_tg on_success: break # Skip re-banning IPs that already have an active decision name: silence_ip_remediation filters: - Alert.Remediation == true && Alert.GetScope() == "Ip" && GetActiveDecisionsCount(Alert.GetValue()) > 0 on_success: break --- # Default IP ban — 500h with Telegram notification name: default_ip_remediation filters: - Alert.Remediation == true && Alert.GetScope() == "Ip" decisions: - type: ban duration: 500h notifications: - http_tg on_success: break --- # Range ban — 500h with Telegram notification name: default_range_remediation filters: - Alert.Remediation == true && Alert.GetScope() == "Range" decisions: - type: ban duration: 500h notifications: - http_tg on_success: break sudo systemctl reload crowdsec # Send a test notification sudo cscli notifications test http_tg sudo systemctl reload crowdsec # Send a test notification sudo cscli notifications test http_tg sudo systemctl reload crowdsec # Send a test notification sudo cscli notifications test http_tg # Agent status and active decisions sudo cscli decisions list sudo cscli alerts list # Bouncer is enforcing bans sudo nft list ruleset | grep crowdsec # Metrics endpoint alive curl -s http://127.0.0.1:6060/metrics | grep cs_active # All services healthy sudo systemctl status crowdsec crowdsec-firewall-bouncer # Agent status and active decisions sudo cscli decisions list sudo cscli alerts list # Bouncer is enforcing bans sudo nft list ruleset | grep crowdsec # Metrics endpoint alive curl -s http://127.0.0.1:6060/metrics | grep cs_active # All services healthy sudo systemctl status crowdsec crowdsec-firewall-bouncer # Agent status and active decisions sudo cscli decisions list sudo cscli alerts list # Bouncer is enforcing bans sudo nft list ruleset | grep crowdsec # Metrics endpoint alive curl -s http://127.0.0.1:6060/metrics | grep cs_active # All services healthy sudo systemctl status crowdsec crowdsec-firewall-bouncer - A server running Ubuntu or any other operating system - sudo access - nftables installed (sudo apt install nftables -y) [iptables will also work. But needs separate setup for that] - Grafana Prometheus for monitoring section - A Telegram account (for the optional notification section) - Create a free account at app.crowdsec.net - Navigate to Engines → Enroll and copy your enrollment key - Run on your server: - Restart CrowdSec: - Go back to the Console and accept the engine from the UI. - In Grafana, go to Dashboards → Import - Use dashboard ID 21419 (Crowdsec Metrics) - Select your Prometheus datasource and click Import - You can also import dashboards from their official github repository here: Crowdsec Grafana Dashboards - Open Telegram and search for @botfather - Send /newbot and follow the prompts — save your bot token - Add the bot to your target group or channel - Get the chat ID:" style="background: linear-gradient(135deg, #6a5acd 0%, #5a4abd 100%); color: #fff; border: none; padding: 6px 12px; border-radius: 8px; cursor: pointer; font-size: 12px; font-weight: 600; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); display: flex; align-items: center; gap: 8px; box-shadow: 0 4px 12px rgba(106, 90, 205, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.1); position: relative; overflow: hidden;">

Copy

# Add the CrowdSec repository -weight: 500;">curl -s https://-weight: 500;">install.crowdsec.net | -weight: 600;">sudo bash # Install the agent -weight: 600;">sudo -weight: 500;">apt -weight: 500;">update && -weight: 600;">sudo -weight: 500;">apt -weight: 500;">install crowdsec -y # Add the CrowdSec repository -weight: 500;">curl -s https://-weight: 500;">install.crowdsec.net | -weight: 600;">sudo bash # Install the agent -weight: 600;">sudo -weight: 500;">apt -weight: 500;">update && -weight: 600;">sudo -weight: 500;">apt -weight: 500;">install crowdsec -y # Add the CrowdSec repository -weight: 500;">curl -s https://-weight: 500;">install.crowdsec.net | -weight: 600;">sudo bash # Install the agent -weight: 600;">sudo -weight: 500;">apt -weight: 500;">update && -weight: 600;">sudo -weight: 500;">apt -weight: 500;">install crowdsec -y -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">status crowdsec -weight: 600;">sudo cscli version -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">status crowdsec -weight: 600;">sudo cscli version -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">status crowdsec -weight: 600;">sudo cscli version -weight: 600;">sudo cscli console enroll <YOUR_ENROLLMENT_KEY> -weight: 600;">sudo cscli console enroll <YOUR_ENROLLMENT_KEY> -weight: 600;">sudo cscli console enroll <YOUR_ENROLLMENT_KEY> -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">restart crowdsec -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">restart crowdsec -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">restart crowdsec -weight: 600;">sudo -weight: 500;">apt -weight: 500;">install crowdsec-firewall-bouncer-nftables -y -weight: 600;">sudo -weight: 500;">apt -weight: 500;">install crowdsec-firewall-bouncer-nftables -y -weight: 600;">sudo -weight: 500;">apt -weight: 500;">install crowdsec-firewall-bouncer-nftables -y -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">enable --now crowdsec-firewall-bouncer -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">enable --now crowdsec-firewall-bouncer -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">enable --now crowdsec-firewall-bouncer -weight: 600;">sudo cscli bouncers list -weight: 600;">sudo cscli bouncers list -weight: 600;">sudo cscli bouncers list # Core -weight: 500;">service collections -weight: 600;">sudo cscli collections -weight: 500;">install crowdsecurity/nginx -weight: 600;">sudo cscli collections -weight: 500;">install crowdsecurity/sshd -weight: 600;">sudo cscli collections -weight: 500;">install crowdsecurity/mysql -weight: 600;">sudo cscli collections -weight: 500;">install crowdsecurity/linux # Optional but recommended -weight: 600;">sudo cscli collections -weight: 500;">install crowdsecurity/http-cve -weight: 600;">sudo cscli collections -weight: 500;">install crowdsecurity/iptables # Core -weight: 500;">service collections -weight: 600;">sudo cscli collections -weight: 500;">install crowdsecurity/nginx -weight: 600;">sudo cscli collections -weight: 500;">install crowdsecurity/sshd -weight: 600;">sudo cscli collections -weight: 500;">install crowdsecurity/mysql -weight: 600;">sudo cscli collections -weight: 500;">install crowdsecurity/linux # Optional but recommended -weight: 600;">sudo cscli collections -weight: 500;">install crowdsecurity/http-cve -weight: 600;">sudo cscli collections -weight: 500;">install crowdsecurity/iptables # Core -weight: 500;">service collections -weight: 600;">sudo cscli collections -weight: 500;">install crowdsecurity/nginx -weight: 600;">sudo cscli collections -weight: 500;">install crowdsecurity/sshd -weight: 600;">sudo cscli collections -weight: 500;">install crowdsecurity/mysql -weight: 600;">sudo cscli collections -weight: 500;">install crowdsecurity/linux # Optional but recommended -weight: 600;">sudo cscli collections -weight: 500;">install crowdsecurity/http-cve -weight: 600;">sudo cscli collections -weight: 500;">install crowdsecurity/iptables # Reload to activate new parsers and scenarios -weight: 600;">sudo -weight: 500;">systemctl reload crowdsec # Reload to activate new parsers and scenarios -weight: 600;">sudo -weight: 500;">systemctl reload crowdsec # Reload to activate new parsers and scenarios -weight: 600;">sudo -weight: 500;">systemctl reload crowdsec -weight: 600;">sudo cscli collections list -weight: 600;">sudo cscli collections list -weight: 600;">sudo cscli collections list # /etc/crowdsec/acquis.yaml filenames: - /var/log/nginx/access.log - /var/log/nginx/error.log labels: type: nginx --- # SSH auth logs filenames: - /var/log/auth.log labels: type: syslog --- # MySQL error log filenames: - /var/log/mysql/error.log labels: type: mysql --- # General syslog and kernel log filenames: - /var/log/syslog - /var/log/kern.log labels: type: syslog # /etc/crowdsec/acquis.yaml filenames: - /var/log/nginx/access.log - /var/log/nginx/error.log labels: type: nginx --- # SSH auth logs filenames: - /var/log/auth.log labels: type: syslog --- # MySQL error log filenames: - /var/log/mysql/error.log labels: type: mysql --- # General syslog and kernel log filenames: - /var/log/syslog - /var/log/kern.log labels: type: syslog # /etc/crowdsec/acquis.yaml filenames: - /var/log/nginx/access.log - /var/log/nginx/error.log labels: type: nginx --- # SSH auth logs filenames: - /var/log/auth.log labels: type: syslog --- # MySQL error log filenames: - /var/log/mysql/error.log labels: type: mysql --- # General syslog and kernel log filenames: - /var/log/syslog - /var/log/kern.log labels: type: syslog -weight: 600;">sudo -weight: 500;">systemctl reload crowdsec -weight: 600;">sudo -weight: 500;">systemctl reload crowdsec -weight: 600;">sudo -weight: 500;">systemctl reload crowdsec -weight: 600;">sudo cscli allowlists create my_allowlist \ --description "Trusted internal and monitoring IPs" -weight: 600;">sudo cscli allowlists create my_allowlist \ --description "Trusted internal and monitoring IPs" -weight: 600;">sudo cscli allowlists create my_allowlist \ --description "Trusted internal and monitoring IPs" # Single IP -weight: 600;">sudo cscli allowlists add my_allowlist 192.168.1.10 \ --comment "Internal monitoring" # CIDR range -weight: 600;">sudo cscli allowlists add my_allowlist 10.0.0.0/8 \ --comment "Private network range" # Multiple IPs at once -weight: 600;">sudo cscli allowlists add my_allowlist \ 203.0.113.5 203.0.113.10 \ --comment "Partner IPs" # Single IP -weight: 600;">sudo cscli allowlists add my_allowlist 192.168.1.10 \ --comment "Internal monitoring" # CIDR range -weight: 600;">sudo cscli allowlists add my_allowlist 10.0.0.0/8 \ --comment "Private network range" # Multiple IPs at once -weight: 600;">sudo cscli allowlists add my_allowlist \ 203.0.113.5 203.0.113.10 \ --comment "Partner IPs" # Single IP -weight: 600;">sudo cscli allowlists add my_allowlist 192.168.1.10 \ --comment "Internal monitoring" # CIDR range -weight: 600;">sudo cscli allowlists add my_allowlist 10.0.0.0/8 \ --comment "Private network range" # Multiple IPs at once -weight: 600;">sudo cscli allowlists add my_allowlist \ 203.0.113.5 203.0.113.10 \ --comment "Partner IPs" -weight: 600;">sudo cscli allowlists list -weight: 600;">sudo cscli allowlists inspect my_allowlist -weight: 600;">sudo cscli allowlists -weight: 500;">remove my_allowlist 203.0.113.5 -weight: 600;">sudo cscli allowlists delete my_allowlist -weight: 600;">sudo cscli allowlists list -weight: 600;">sudo cscli allowlists inspect my_allowlist -weight: 600;">sudo cscli allowlists -weight: 500;">remove my_allowlist 203.0.113.5 -weight: 600;">sudo cscli allowlists delete my_allowlist -weight: 600;">sudo cscli allowlists list -weight: 600;">sudo cscli allowlists inspect my_allowlist -weight: 600;">sudo cscli allowlists -weight: 500;">remove my_allowlist 203.0.113.5 -weight: 600;">sudo cscli allowlists delete my_allowlist mode: nftables update_frequency: 10s log_mode: file log_dir: /var/log/ log_level: info log_compression: true log_max_size: 100 log_max_backups: 3 log_max_age: 30 api_url: http://127.0.0.1:8080/ api_key: <BOUNCER_API_KEY> insecure_skip_verify: false disable_ipv6: false deny_action: DROP deny_log: false supported_decisions_types: - ban blacklists_ipv4: crowdsec-blacklists blacklists_ipv6: crowdsec6-blacklists ipset_type: nethash

nftables nftables: ipv4: enabled: true set-only: false table: crowdsec chain: crowdsec-chain priority: -10 ipv6: enabled: true set-only: false table: crowdsec6 chain: crowdsec6-chain priority: -10 nftables_hooks: - input - forward # packet filter pf: # an empty string disables the anchor anchor_name: "" mode: nftables update_frequency: 10s log_mode: file log_dir: /var/log/ log_level: info log_compression: true log_max_size: 100 log_max_backups: 3 log_max_age: 30 api_url: http://127.0.0.1:8080/ api_key: <BOUNCER_API_KEY> insecure_skip_verify: false disable_ipv6: false deny_action: DROP deny_log: false supported_decisions_types: - ban blacklists_ipv4: crowdsec-blacklists blacklists_ipv6: crowdsec6-blacklists ipset_type: nethash

nftables nftables: ipv4: enabled: true set-only: false table: crowdsec chain: crowdsec-chain priority: -10 ipv6: enabled: true set-only: false table: crowdsec6 chain: crowdsec6-chain priority: -10 nftables_hooks: - input - forward # packet filter pf: # an empty string disables the anchor anchor_name: "" mode: nftables update_frequency: 10s log_mode: file log_dir: /var/log/ log_level: info log_compression: true log_max_size: 100 log_max_backups: 3 log_max_age: 30 api_url: http://127.0.0.1:8080/ api_key: <BOUNCER_API_KEY> insecure_skip_verify: false disable_ipv6: false deny_action: DROP deny_log: false supported_decisions_types: - ban blacklists_ipv4: crowdsec-blacklists blacklists_ipv6: crowdsec6-blacklists ipset_type: nethash

nftables nftables: ipv4: enabled: true set-only: false table: crowdsec chain: crowdsec-chain priority: -10 ipv6: enabled: true set-only: false table: crowdsec6 chain: crowdsec6-chain priority: -10 nftables_hooks: - input - forward # packet filter pf: # an empty string disables the anchor anchor_name: "" -weight: 600;">sudo cscli bouncers list # Regenerate if needed -weight: 600;">sudo cscli bouncers delete crowdsec-firewall-bouncer -weight: 600;">sudo cscli bouncers add crowdsec-firewall-bouncer -weight: 600;">sudo cscli bouncers list # Regenerate if needed -weight: 600;">sudo cscli bouncers delete crowdsec-firewall-bouncer -weight: 600;">sudo cscli bouncers add crowdsec-firewall-bouncer -weight: 600;">sudo cscli bouncers list # Regenerate if needed -weight: 600;">sudo cscli bouncers delete crowdsec-firewall-bouncer -weight: 600;">sudo cscli bouncers add crowdsec-firewall-bouncer -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">restart crowdsec-firewall-bouncer -weight: 600;">sudo nft list ruleset | grep crowdsec -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">restart crowdsec-firewall-bouncer -weight: 600;">sudo nft list ruleset | grep crowdsec -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">restart crowdsec-firewall-bouncer -weight: 600;">sudo nft list ruleset | grep crowdsec prometheus: enabled: true level: full listen_addr: 127.0.0.1 listen_port: 6060 prometheus: enabled: true level: full listen_addr: 127.0.0.1 listen_port: 6060 prometheus: enabled: true level: full listen_addr: 127.0.0.1 listen_port: 6060 -weight: 600;">sudo -weight: 500;">systemctl reload crowdsec -weight: 500;">curl -s http://127.0.0.1:6060/metrics | head -20 -weight: 600;">sudo -weight: 500;">systemctl reload crowdsec -weight: 500;">curl -s http://127.0.0.1:6060/metrics | head -20 -weight: 600;">sudo -weight: 500;">systemctl reload crowdsec -weight: 500;">curl -s http://127.0.0.1:6060/metrics | head -20 scrape_configs: - job_name: 'crowdsec' static_configs: - targets: ['localhost:6060'] scrape_interval: 30s scrape_configs: - job_name: 'crowdsec' static_configs: - targets: ['localhost:6060'] scrape_interval: 30s scrape_configs: - job_name: 'crowdsec' static_configs: - targets: ['localhost:6060'] scrape_interval: 30s -weight: 600;">sudo -weight: 500;">systemctl reload prometheus # Or via the HTTP API if enabled: -weight: 500;">curl -X POST http://localhost:9090/-/reload -weight: 600;">sudo -weight: 500;">systemctl reload prometheus # Or via the HTTP API if enabled: -weight: 500;">curl -X POST http://localhost:9090/-/reload -weight: 600;">sudo -weight: 500;">systemctl reload prometheus # Or via the HTTP API if enabled: -weight: 500;">curl -X POST http://localhost:9090/-/reload -weight: 500;">curl -s https://api.telegram.org/bot<BOT_TOKEN>/getUpdates \ | python3 -m json.tool | grep '"id"' -weight: 500;">curl -s https://api.telegram.org/bot<BOT_TOKEN>/getUpdates \ | python3 -m json.tool | grep '"id"' -weight: 500;">curl -s https://api.telegram.org/bot<BOT_TOKEN>/getUpdates \ | python3 -m json.tool | grep '"id"' # Author: Nirjas Jakilim type: http name: http_tg log_level: info format: | { "chat_id": "-YOUR_CHAT_ID", "text": " {{range . -}} {{$alert := . -}} {{range .Decisions -}} {{if $alert.Source.Cn -}} Scenario: {{.Scenario}} IP: {{.Value}} Ban Duration: {{.Duration}} Country: {{$alert.Source.Cn}} AS Name: {{$alert.Source.AsName}} {{end}} {{if not $alert.Source.Cn -}} Scenario: {{.Scenario}} IP: {{.Value}} Ban Duration: {{.Duration}} Country: Unknown {{end}} {{end -}} {{end -}} ", "reply_markup": { "inline_keyboard": [ {{ $arrLength := len . -}} {{ range $i, $value := . -}} {{ $V := $value.Source.Value -}} [ { "text": "See {{ $V }} on shodan.io", "url": "https://www.shodan.io/host/{{ $V }}" }, { "text": "See {{ $V }} on crowdsec.net", "url": "https://app.crowdsec.net/cti/{{ $V }}" } ]{{if lt $i ( sub $arrLength 1) }},{{end }} {{end -}} ] } } url: https://api.telegram.org/bot<BOT_TOKEN>/sendMessage method: POST headers: Content-Type: "application/json" # Author: Nirjas Jakilim type: http name: http_tg log_level: info format: | { "chat_id": "-YOUR_CHAT_ID", "text": " {{range . -}} {{$alert := . -}} {{range .Decisions -}} {{if $alert.Source.Cn -}} Scenario: {{.Scenario}} IP: {{.Value}} Ban Duration: {{.Duration}} Country: {{$alert.Source.Cn}} AS Name: {{$alert.Source.AsName}} {{end}} {{if not $alert.Source.Cn -}} Scenario: {{.Scenario}} IP: {{.Value}} Ban Duration: {{.Duration}} Country: Unknown {{end}} {{end -}} {{end -}} ", "reply_markup": { "inline_keyboard": [ {{ $arrLength := len . -}} {{ range $i, $value := . -}} {{ $V := $value.Source.Value -}} [ { "text": "See {{ $V }} on shodan.io", "url": "https://www.shodan.io/host/{{ $V }}" }, { "text": "See {{ $V }} on crowdsec.net", "url": "https://app.crowdsec.net/cti/{{ $V }}" } ]{{if lt $i ( sub $arrLength 1) }},{{end }} {{end -}} ] } } url: https://api.telegram.org/bot<BOT_TOKEN>/sendMessage method: POST headers: Content-Type: "application/json" # Author: Nirjas Jakilim type: http name: http_tg log_level: info format: | { "chat_id": "-YOUR_CHAT_ID", "text": " {{range . -}} {{$alert := . -}} {{range .Decisions -}} {{if $alert.Source.Cn -}} Scenario: {{.Scenario}} IP: {{.Value}} Ban Duration: {{.Duration}} Country: {{$alert.Source.Cn}} AS Name: {{$alert.Source.AsName}} {{end}} {{if not $alert.Source.Cn -}} Scenario: {{.Scenario}} IP: {{.Value}} Ban Duration: {{.Duration}} Country: Unknown {{end}} {{end -}} {{end -}} ", "reply_markup": { "inline_keyboard": [ {{ $arrLength := len . -}} {{ range $i, $value := . -}} {{ $V := $value.Source.Value -}} [ { "text": "See {{ $V }} on shodan.io", "url": "https://www.shodan.io/host/{{ $V }}" }, { "text": "See {{ $V }} on crowdsec.net", "url": "https://app.crowdsec.net/cti/{{ $V }}" } ]{{if lt $i ( sub $arrLength 1) }},{{end }} {{end -}} ] } } url: https://api.telegram.org/bot<BOT_TOKEN>/sendMessage method: POST headers: Content-Type: "application/json" # Skip re-banning IPs that already have an active decision name: silence_ip_remediation filters: - Alert.Remediation == true && Alert.GetScope() == "Ip" && GetActiveDecisionsCount(Alert.GetValue()) > 0 on_success: break --- # Default IP ban — 500h with Telegram notification name: default_ip_remediation filters: - Alert.Remediation == true && Alert.GetScope() == "Ip" decisions: - type: ban duration: 500h notifications: - http_tg on_success: break --- # Range ban — 500h with Telegram notification name: default_range_remediation filters: - Alert.Remediation == true && Alert.GetScope() == "Range" decisions: - type: ban duration: 500h notifications: - http_tg on_success: break # Skip re-banning IPs that already have an active decision name: silence_ip_remediation filters: - Alert.Remediation == true && Alert.GetScope() == "Ip" && GetActiveDecisionsCount(Alert.GetValue()) > 0 on_success: break --- # Default IP ban — 500h with Telegram notification name: default_ip_remediation filters: - Alert.Remediation == true && Alert.GetScope() == "Ip" decisions: - type: ban duration: 500h notifications: - http_tg on_success: break --- # Range ban — 500h with Telegram notification name: default_range_remediation filters: - Alert.Remediation == true && Alert.GetScope() == "Range" decisions: - type: ban duration: 500h notifications: - http_tg on_success: break # Skip re-banning IPs that already have an active decision name: silence_ip_remediation filters: - Alert.Remediation == true && Alert.GetScope() == "Ip" && GetActiveDecisionsCount(Alert.GetValue()) > 0 on_success: break --- # Default IP ban — 500h with Telegram notification name: default_ip_remediation filters: - Alert.Remediation == true && Alert.GetScope() == "Ip" decisions: - type: ban duration: 500h notifications: - http_tg on_success: break --- # Range ban — 500h with Telegram notification name: default_range_remediation filters: - Alert.Remediation == true && Alert.GetScope() == "Range" decisions: - type: ban duration: 500h notifications: - http_tg on_success: break -weight: 600;">sudo -weight: 500;">systemctl reload crowdsec # Send a test notification -weight: 600;">sudo cscli notifications test http_tg -weight: 600;">sudo -weight: 500;">systemctl reload crowdsec # Send a test notification -weight: 600;">sudo cscli notifications test http_tg -weight: 600;">sudo -weight: 500;">systemctl reload crowdsec # Send a test notification -weight: 600;">sudo cscli notifications test http_tg # Agent -weight: 500;">status and active decisions -weight: 600;">sudo cscli decisions list -weight: 600;">sudo cscli alerts list # Bouncer is enforcing bans -weight: 600;">sudo nft list ruleset | grep crowdsec # Metrics endpoint alive -weight: 500;">curl -s http://127.0.0.1:6060/metrics | grep cs_active # All services healthy -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">status crowdsec crowdsec-firewall-bouncer # Agent -weight: 500;">status and active decisions -weight: 600;">sudo cscli decisions list -weight: 600;">sudo cscli alerts list # Bouncer is enforcing bans -weight: 600;">sudo nft list ruleset | grep crowdsec # Metrics endpoint alive -weight: 500;">curl -s http://127.0.0.1:6060/metrics | grep cs_active # All services healthy -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">status crowdsec crowdsec-firewall-bouncer # Agent -weight: 500;">status and active decisions -weight: 600;">sudo cscli decisions list -weight: 600;">sudo cscli alerts list # Bouncer is enforcing bans -weight: 600;">sudo nft list ruleset | grep crowdsec # Metrics endpoint alive -weight: 500;">curl -s http://127.0.0.1:6060/metrics | grep cs_active # All services healthy -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">status crowdsec crowdsec-firewall-bouncer - A server running Ubuntu or any other operating system - -weight: 600;">sudo access - nftables installed (-weight: 600;">sudo -weight: 500;">apt -weight: 500;">install nftables -y) [iptables will also work. But needs separate setup for that] - Grafana Prometheus for monitoring section - A Telegram account (for the optional notification section) - Create a free account at app.crowdsec.net - Navigate to Engines → Enroll and copy your enrollment key - Run on your server: - Restart CrowdSec: - Go back to the Console and accept the engine from the UI. - In Grafana, go to Dashboards → Import - Use dashboard ID 21419 (Crowdsec Metrics) - Select your Prometheus datasource and click Import - You can also import dashboards from their official github repository here: Crowdsec Grafana Dashboards - Open Telegram and search for @botfather - Send /newbot and follow the prompts — save your bot token - Add the bot to your target group or channel - Get the chat ID: