Mahbubur Riad
Back to blog
Hosting & Server 6 min read

Step-by-Step Guide: Securing a Linux VPS with CrowdSec, Fail2Ban, and Apache ModSecurity

Jun 17, 2026 · Mahbubur Riad

Hardening your Linux VPS against brute force and web attacks using CrowdSec (real-time BDF), Fail2Ban (classic jail), and Apache ModSecurity + OWASP CRS. Practical, tested, no fluff.

On this page

Step-by-Step Guide: Securing a Linux VPS with CrowdSec, Fail2Ban, and Apache ModSecurity (OWASP CRS)

If you’re running a low-cost VPS — say, a $5/month Ubuntu instance — and hosting even a small web app or API, you will get scanned. Constantly.

I’ve seen it: after spinning up a new droplet, within minutes of opening port 22 or 80, you’ll see SSH brute-force attempts, WordPress login floods, SQL injection probes, and port scanners poking around. It’s not personal — it’s the internet.

The good news? You don’t need $200/month WAFs or enterprise firewalls. With CrowdSec, Fail2Ban, and Apache ModSecurity + OWASP CRS, you can build a robust, layered defense stack — all free, open-source, and lightweight enough to run on a 512MB RAM VPS.

Let me walk you through how I harden every VPS I deploy — no theory, just what actually works in production.


Why Layer Security? (CrowdSec + Fail2Ban + ModSecurity)

Before we dive in: why use three tools instead of one?

  • Fail2Ban is reliable, lightweight, and great for SSH, SMTP, and basic HTTP brute force. But it’s reactive and per-service — it doesn’t understand web payloads.
  • CrowdSec is newer but smarter: it’s a behavioral detection engine. It correlates events across services, uses community threat intelligence (the Bouncer system), and can auto-block IPs at the firewall level (iptables/nftables).
  • ModSecurity + OWASP CRS is your last line of defense at the application layer — it inspects HTTP traffic, blocks SQLi, XSS, LFI, etc., in real time.

Together, they cover:

  • Network-level abuse (SSH, FTP, etc.) → Fail2Ban + CrowdSec
  • Web-layer attacks (SQLi, XSS, RCE) → ModSecurity + CRS
  • Global threat context (e.g., known botnets, Tor exit nodes) → CrowdSec

They complement each other — and yes, they can run side-by-side without conflict (we’ll configure that).


Prerequisites

  • A fresh Ubuntu 22.04 or 24.04 VPS (Debian-based works similarly)
  • Root or sudo access
  • Apache installed (apt install apache2)
  • Basic familiarity with bash, systemd, and config files

💡 Note: If you’re using Nginx, skip ModSecurity for now — I’ll cover it in a future post. This guide assumes Apache.


Step 1: Install & Configure Fail2Ban (The Classic Jail)

Fail2Ban is still the most straightforward way to block repeated SSH attacks.

Install

Bash
apt update && apt install fail2ban -y

Configure

Copy the default config to a local override:

Bash
cp /etc/fail2ban/jail.local /etc/fail2ban/jail.local.orig  # backup
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Edit jail.local:

Bash
nano /etc/fail2ban/jail.local

Critical settings to tweak:

INI
[DEFAULT]
# Ban IPs for 10 minutes (600s) — short enough to reset, long enough to deter
bantime = 600
findtime = 600    # look back 10 minutes
maxretry = 5      # 5 failures → ban

# Enable SSH jail (usually enabled by default)
[sshd]
enabled = true
port    = ssh
logpath = %(sshd_log)s
maxretry = 3      # stricter for SSH

# Optional: protect Apache (basic)
[apache-auth]
enabled = true
port    = http,https
logpath = /var/log/apache2/error.log
maxretry = 6

⚠️ Don’t overdo maxretry — too high = too slow to react. Too low = risk of locking yourself out during config.

Restart and enable:

Bash
systemctl restart fail2ban
systemctl enable fail2ban

Check status:

Bash
fail2ban-client status sshd

You’ll see active bans if any. If not — good! You’re not getting hammered yet.


Step 2: Install & Configure CrowdSec (The Behavioral Sentinel)

CrowdSec is where things get interesting. It’s not just IP blocking — it’s behavioral analysis.

Install

Bash
curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash
apt install crowdsec

Initialize & Enable

Run the interactive setup:

Bash
crowdsec

Answer prompts:

  • Accept default database (/var/lib/crowdsec/data/crowdsec.db)
  • Enable local API (yes)
  • Enable CLI (yes)

Now, install the bouncers — the components that enforce blocks at the firewall or proxy level.

Install the iptables bouncer (for network-level blocking):

Bash
apt install crowdsec-firewall-bouncer-iptables

This writes rules into iptables — no extra process needed.

Enable the nginx/apache bouncer (optional — we’ll use ModSecurity for deep web inspection instead)

Skip this for now — ModSecurity is more precise for HTTP attacks.

Test CrowdSec

Start the service:

Bash
systemctl start crowdsec
systemctl enable crowdsec

Generate a fake SSH brute-force (only in a test environment!):

Bash
for i in {1..6}; do ssh root@localhost 2>&1 | grep -q "Permission denied" || true; done

Check logs:

Bash
tail -f /var/log/crowdsec.log

You should see a decision being made.

Check active decisions:

Bash
crowdsec-cli decisions list

You’ll see something like:

Text
+----------------------------------+----------+----------+------------+-----------+
|         Value                    |  Type    |  Action  |  Duration  |  Source   |
+----------------------------------+----------+----------+------------+-----------+
| 192.168.1.100                   |  ip      |  ban     |  1h0m0s    | crowdsec |
+----------------------------------+----------+----------+------------+-----------+

✅ CrowdSec is blocking — and it’s using behavioral context, not just raw counts.


Step 3: Install & Configure ModSecurity + OWASP CRS (Web Firewall)

Now for the heavy hitter: ModSecurity. It’s Apache’s WAF — and with the OWASP Core Rule Set (CRS), you get battle-tested rules for SQLi, XSS, RCE, etc.

Install ModSecurity

Bash
apt install libapache2-mod-security2 -y

Enable the module:

Bash
a2enmod security2

Download & Install OWASP CRS

Bash
cd /etc/apache2
git clone https://github.com/coreruleset/coreruleset.git crs

Configure ModSecurity

Edit the main config:

Bash
nano /etc/apache2/mods-available/security2.conf

Replace the default rules with this minimal but solid setup:

APACHE
<IfModule mod_security2.c>
    # Base config
    SecRuleEngine On
    SecRequestBodyAccess On
    SecRequestBodyLimit 13107200
    SecRequestBodyNoFilesLimit 131072
    SecRequestBodyLimitAction Reject
    SecResponseBodyAccess Off

    # Logging
    SecDebugLog /var/log/apache2/modsec_debug.log
    SecDebugLogLevel 3

    # Include CRS
    IncludeOptional crs/crs-setup.conf
    IncludeOptional crs/rules/*.conf
</IfModule>

🔍 Important: SecRequestBodyLimit is set to ~12.5MB. Adjust if you run file uploads — but keep it low unless needed.

Configure OWASP CRS

Edit crs-setup.conf:

Bash
nano /etc/apache2/crs/crs-setup.conf

Tweak these (comment out defaults if needed):

PERL
# Set paranoia level: 1 = low false positives, 4 = high (use 1–2 for production)
SecAction "id:900110,phase:1,nolog,pass,ctl:paranoia_level=1"

# Set attack detection mode: 1 = anomaly mode (default, recommended)
SecAction "id:900120,phase:1,nolog,pass,ctl:rule_engine=On"

# Whitelist your own IP to avoid false positives during testing
SecRule REMOTE_ADDR "@ipMatch 192.168.1.100" "id:900000,phase:1,nolog,pass,ctl:ruleEngine=Off"

Pro tip: Start with paranoia_level=1. Level 2+ catches more, but will trigger false positives on common CMS (e.g., WordPress permalinks). You can upgrade later.

Restart Apache

Bash
apachectl configtest
systemctl reload apache2

Check logs:

Bash
tail -f /var/log/apache2/modsec_debug.log

If you see Access denied with code 403 (phase 2). Matched phrase "Attack detected" — it’s working.

To test safely, try a harmless SQLi probe (on a test page):

Text
https://your-vps/test.php?id=1' OR '1'='1

You should get a 403 — and the rule ID in the log.


Step 4: Coordinate Fail2Ban & CrowdSec (No Overlap)

You can run both — but avoid double-banning.

  • Disable Fail2Ban’s firewall actions (it still logs/jails, but won’t write iptables rules).
  • CrowdSec’s iptables bouncer handles all bans.

In /etc/fail2ban/jail.local:

INI
[DEFAULT]
# Disable firewall actions
banaction = iptables-multiport
# → Change to:
banaction = none

Then restart Fail2Ban:

Bash
systemctl restart fail2ban

✅ This keeps Fail2Ban’s rich logging (great for auditing) while offloading enforcement to CrowdSec.

Option 2: Let Fail2Ban do SSH, CrowdSec do web/other

  • Keep Fail2Ban for [sshd]
  • Disable CrowdSec’s iptables bouncer (or use only CrowdSec for web)

But this adds complexity — and CrowdSec is better at contextual detection.

I recommend Option 1 unless you really need Fail2Ban’s SSH-specific tuning.


Comparison: CrowdSec vs Fail2Ban vs ModSecurity

Feature CrowdSec Fail2Ban ModSecurity + CRS
Layer Network + Behavioral Network (service logs) Application (HTTP)
Real-time? ✅ Yes (live analysis) ❌ Delayed (log polling) ✅ Yes (request-by-request)
Global context ✅ Crowd intelligence ❌ Local only ❌ Local rules only
False positives Low (configurable) Medium (fixed thresholds) Medium-High (tuning needed)
Resource usage ~50–100MB RAM ~20MB RAM ~80–150MB RAM (CRS)
Best for Proactive threat blocking SSH/FTP brute force SQLi/XSS/LFI protection
Requires tuning? Minimal Low High (paranoia levels, exclusions)

💡 My stack: CrowdSec for global blocking + Fail2Ban for SSH + ModSecurity for web.


Common Pitfalls & Fixes

❌ “I’m getting blocked but I’m not an attacker!”

  • Fix: Whitelist your IP in CrowdSec and CRS.
    • In crs-setup.conf: SecRule REMOTE_ADDR "@ipMatch YOUR_IP" "id:900000,phase:1,nolog,pass,ctl:ruleEngine=Off"
    • In CrowdSec: crowdsec-cli whitelist add --ip YOUR_IP --reason "home office"

❌ “ModSecurity blocks everything!”

  • Fix: Start with paranoia_level=1, and check logs before blaming rules.
  • Use SecRuleRemoveById in a custom file (e.g., /etc/apache2/mods-enabled/security2-local.conf) to selectively disable rules.

❌ “CrowdSec doesn’t block SSH attacks!”

  • Fix: Ensure crowdsec-firewall-bouncer-iptables is installed and running (systemctl status crowdsec-firewall-bouncer-iptables).
  • Check /var/log/crowdsec.log for decision added on SSH events.

Maintenance & Monitoring

  • Check CrowdSec daily:

    Bash
    crowdsec-cli decisions list --active
    
  • Rotate logs — ModSecurity + CrowdSec logs grow fast. Use logrotate:

    Bash
    cat > /etc/logrotate.d/modsec <<'EOF'
    /var/log/apache2/modsec*.log {
        daily
        rotate 14
        compress
        delaycompress
        missingok
        notifempty
        create 0640 root adm
    }
    EOF
    
  • Update rules:

    • CrowdSec: automatic (via crowdsec service)
    • ModSecurity CRS: cd /etc/apache2/crs && git pull (weekly)
    • Fail2Ban: restart after config changes

FAQ

1. Can I use CrowdSec without Fail2Ban?

Yes — CrowdSec can parse Apache/SSH logs directly. But Fail2Ban’s mature filters (e.g., apache-botscout) are still useful. Run both if you want layered logging.

2. Does ModSecurity slow down my site?

With paranoia_level=1 and basic CRS rules, latency is usually <1ms. On a 1GB RAM VPS, you won’t notice. Avoid paranoia_level=4.

3. What if I get false positives on my API?

Use ModSecurity’s ctl:ruleEngine=Off on specific endpoints:

APACHE
<LocationMatch "^/api/.*">
  SecRuleEngine Off
</LocationMatch>

Or whitelist Content-Type: application/json for your API routes.

4. Is this enough for GDPR/PCI-DSS?

No. This is baseline hardening — not a compliance solution. Use it as a foundation, then add logging aggregation (e.g., ELK), WAF reporting, and audit trails.

5. How do I unban an IP?

  • CrowdSec: crowdsec-cli decisions delete --ip IP
  • Fail2Ban: fail2ban-client unban IP
  • ModSecurity: no manual unban — rules auto-expire or you restart Apache.

Final Thoughts

Hardening a VPS isn’t about adding more firewalls. It’s about reducing blast radius — and these tools do exactly that.

CrowdSec blocks known bad actors before they hit your server. Fail2Ban catches the low-hanging SSH bots. ModSecurity + CRS stops the clever ones who get past the first two layers.

It’s not perfect — but it’s realistic. You won’t get 100% protection (nothing does), but you’ll survive the daily noise.

I’ve used this stack on 50+ VPSes — from a $5 Ubuntu box to a $40/month Kubernetes node. It scales down.

If you’re just starting out: install CrowdSec + ModSecurity + CRS first. Add Fail2Ban later if you need SSH-specific tuning.

Security isn’t a one-time setup — it’s a habit. One rule at a time.


This guide is based on real deployments on Ubuntu 22.04/24.04. If you hit a snag, check the logs — they’ll tell you the story. And if you found this useful, I write more on practical sysadmin work at mahbuburriad.com.

Related

Related posts