The Best Price for IPv4/IPv6 Lease – Any RIR & Any Geo-LocationOrder Now
Hostperl

Configure ModSecurity WAF on Ubuntu VPS: Step-by-Step

By Raman Kumar

Share:

Updated on Jun 29, 2026

Configure ModSecurity WAF on Ubuntu VPS: Step-by-Step

What ModSecurity Actually Does for a Hosting Server

A firewall blocks ports. ModSecurity inspects what's coming through them. It's a web application firewall (WAF) that sits in front of Apache and examines every HTTP request before it reaches your application — checking for SQL injection attempts, cross-site scripting payloads, path traversal tricks, and dozens of other attack patterns.

If you're running WordPress, WooCommerce, or any PHP application on a Hostperl VPS, ModSecurity with the OWASP Core Rule Set (CRS) is one of the most practical security layers you can add. It won't replace good passwords or timely updates, but it catches a lot of automated garbage that would otherwise reach your site's code.

This tutorial walks through a full installation on Ubuntu 22.04 or 24.04 LTS with Apache, including CRS setup and tuning to avoid blocking legitimate traffic.

Before You Begin

You'll need:

  • Ubuntu 22.04 or 24.04 VPS with root or sudo access
  • Apache 2.4 already installed and running
  • At least one virtual host configured for your domain
  • A basic familiarity with editing config files over SSH

If your firewall isn't locked down yet, run through the UFW firewall setup guide first. ModSecurity handles application-layer threats; UFW handles the network layer. Both matter.

Step 1: Install ModSecurity and the Apache Connector

Ubuntu's package repositories include ModSecurity 2.x, which is the stable, widely-deployed version compatible with Apache 2.4. Install it along with the Apache connector module:

sudo apt update
sudo apt install libapache2-mod-security2 -y

Once that completes, enable the module:

sudo a2enmod security2
sudo systemctl restart apache2

Confirm ModSecurity loaded without errors:

sudo apachectl -M | grep security

You should see security2_module (shared) in the output. If Apache threw an error on restart, check /var/log/apache2/error.log — the most common cause is a syntax issue in an existing config file unrelated to ModSecurity.

Step 2: Enable the Default Configuration

ModSecurity ships with a recommended config file, but it's named with a .recommended extension and won't load automatically. Copy it into place:

sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf

Open the file and change the engine mode from detection-only to active enforcement:

sudo nano /etc/modsecurity/modsecurity.conf

Find this line:

SecRuleEngine DetectionOnly

Change it to:

SecRuleEngine On

While you're in this file, also verify that SecRequestBodyAccess is set to On — this ensures POST body content (form submissions, API payloads) gets inspected, not just the URL and headers.

Save and close the file, but don't restart Apache yet. You'll add the OWASP rules first.

Step 3: Install the OWASP Core Rule Set

ModSecurity on its own has no rules — it's an engine. The OWASP Core Rule Set (CRS) is the standard ruleset that does the actual detection work. As of 2026, CRS 4.x is current and actively maintained.

Install it from the package repo:

sudo apt install modsecurity-crs -y

This places the rules in /usr/share/modsecurity-crs/. Now tell ModSecurity where to find them. Open the security2 module configuration:

sudo nano /etc/apache2/mods-enabled/security2.conf

Check that these lines are present and uncommented:

IncludeOptional /etc/modsecurity/*.conf
IncludeOptional /usr/share/modsecurity-crs/*.conf
IncludeOptional /usr/share/modsecurity-crs/rules/*.conf

If the CRS lines aren't there, add them. Save the file, then copy the CRS setup config:

sudo cp /usr/share/modsecurity-crs/crs-setup.conf.example /usr/share/modsecurity-crs/crs-setup.conf

Now restart Apache:

sudo systemctl restart apache2

If Apache starts cleanly, ModSecurity is running with OWASP rules active.

Step 4: Verify ModSecurity Is Blocking Threats

Run a quick test from your local machine or another server. The following curl command sends a classic SQL injection string in the URL:

curl -I "http://your-server-ip/?id=1+UNION+SELECT+1,2,3--"

ModSecurity should return a 403 Forbidden response. If you still get a 200 OK, the engine isn't enforcing — double-check that SecRuleEngine On is saved correctly and Apache was restarted afterward.

You can also confirm detections are being logged:

sudo tail -f /var/log/apache2/modsec_audit.log

Each blocked request gets a full entry here, including which rule triggered, the matched data, and the client IP. This log becomes useful later for tuning false positives.

Step 5: Handle False Positives Without Disabling Rules

The CRS is thorough, which means some legitimate requests will occasionally trigger it — especially in WordPress admin panels, e-commerce checkouts, or apps that accept rich text input. The mistake many admins make is lowering the paranoia level globally or disabling entire rule groups. That undermines the point.

The cleaner approach is targeted exclusions. Create a custom rules file to keep your changes separate from the packaged config:

sudo nano /etc/modsecurity/custom-rules.conf

To whitelist a specific IP (your office or agency IP) from all ModSecurity inspection:

SecRule REMOTE_ADDR "@ipMatch 203.0.113.45" \
    "id:1001,phase:1,pass,nolog,ctl:ruleEngine=Off"

To disable a specific rule by ID for a particular path only — for example, rule 941100 (XSS detection) on the WordPress post editor:

SecRule REQUEST_URI "@beginsWith /wp-admin/post.php" \
    "id:1002,phase:1,pass,nolog,ctl:ruleRemoveById=941100"

Find the offending rule ID in your audit log — it's listed under id in each rule match entry. This surgical approach lets you keep protection elsewhere while clearing the path for known-good traffic.

After adding exclusions, always test Apache config before restarting:

sudo apachectl configtest && sudo systemctl reload apache2

Step 6: Tune the Paranoia Level

CRS uses a paranoia level (PL) from 1 to 4 that controls how aggressively it applies rules. PL1 catches common attacks with minimal false positives. PL4 is very strict and will block edge-case payloads that most applications never send legitimately.

For a standard WordPress or PHP hosting environment, PL2 is a reasonable starting point — it adds more SQL injection and XSS patterns without becoming overwhelming. Open the CRS setup file:

sudo nano /usr/share/modsecurity-crs/crs-setup.conf

Find and set:

SecAction \
    "id:900000,\
    phase:1,\
    pass,\
    t:none,\
    nolog,\
    tag:'OWASP_CRS',\
    setvar:tx.paranoia_level=2"

Start at PL1 in production, review the audit log for a week, then move to PL2 once you've cleared known false positives. Jumping straight to PL3 on a live site is a reliable way to break things for real customers.

Step 7: Set Up Log Rotation for Audit Logs

The ModSecurity audit log grows quickly on a busy server. Without rotation, it can fill a disk partition in days on a site with moderate traffic. Ubuntu's logrotate handles this, but you need to add a config for the ModSecurity log specifically.

Create a logrotate config:

sudo nano /etc/logrotate.d/modsecurity

Add:

/var/log/apache2/modsec_audit.log {
    daily
    rotate 14
    compress
    delaycompress
    missingok
    notifempty
    sharedscripts
    postrotate
        /usr/bin/systemctl reload apache2 > /dev/null 2>&1 || true
    endscript
}

This keeps 14 days of compressed audit logs and rotates daily. If your server handles high request volumes, consider reducing to 7 days and adjusting SecAuditLogType in modsecurity.conf to Concurrent for better I/O performance. For a broader look at log management on Ubuntu, the logrotate setup guide for Ubuntu VPS covers the full configuration in detail.

Step 8: Enable Per-Virtual Host Control

If you're hosting multiple sites on one VPS, you may want ModSecurity on some virtual hosts but not others — or different paranoia levels per domain. Add ModSecurity directives directly inside a VirtualHost block:

<VirtualHost *:443>
    ServerName example.com
    # ... other directives ...

    # Disable ModSecurity for this vhost only
    SecRuleEngine Off
</VirtualHost>

Or to set a lower anomaly threshold for a staging site:

<VirtualHost *:443>
    ServerName staging.example.com
    SecAction "id:1010,phase:1,pass,nolog,setvar:tx.inbound_anomaly_score_threshold=10"
</VirtualHost>

This kind of per-site control is especially useful for agencies managing client sites where one application may be well-hardened and another is still in development. For more on managing multiple virtual hosts cleanly, the Apache virtual hosts guide is worth reading alongside this one.

Confirming Everything Is Working

Run through this checklist before considering the setup complete:

  • Apache starts cleanlysudo systemctl status apache2 shows active with no errors
  • SQL injection test returns 403 — verified with the curl test from Step 4
  • Audit log is writing/var/log/apache2/modsec_audit.log contains entries
  • Logrotate config is valid — run sudo logrotate --debug /etc/logrotate.d/modsecurity
  • Your own site functions normally — log in to WP admin, submit a contact form, complete a checkout
  • No false positives in the audit log for your own normal usage

If legitimate requests are being blocked, trace the rule ID in the audit log and add a targeted exclusion in /etc/modsecurity/custom-rules.conf as shown in Step 5. Don't disable the engine — work through false positives one rule at a time.

Running ModSecurity on a shared host isn't possible — you need server-level access to install and configure it. If your current hosting plan doesn't give you that control, a Hostperl VPS gives you full root access, SSD storage, and the flexibility to run the exact security stack your applications need. For high-traffic or compliance-sensitive workloads, our dedicated server plans remove the shared-resource constraints entirely. Our support team can help you migrate existing sites without downtime.

Frequently Asked Questions

Does ModSecurity slow down my site?

At paranoia level 1 or 2, the overhead is typically 1–3ms per request on a modern VPS — negligible for most sites. At PL4 with a large rule set, you may notice a few extra milliseconds on complex requests. For most hosting use cases, the tradeoff is well worth it.

Can I use ModSecurity with Nginx instead of Apache?

Yes, but the setup is different. Nginx doesn't support dynamic modules the same way, so you typically need to compile ModSecurity as a connector module or use the Nginx-ModSecurity connector. The OWASP rules themselves are identical. This tutorial specifically covers Apache.

Will ModSecurity protect against WordPress plugin vulnerabilities?

Partially. It won't patch a vulnerable plugin, but it can block exploit attempts that use common payload patterns — SQL injection, file inclusion, XSS. Virtual patching (writing a custom ModSecurity rule to block a known CVE) is a legitimate short-term fix while you wait for an upstream patch, but it's not a substitute for keeping plugins updated. For more on keeping your VPS environment secure beyond WAF rules, the VPS security checklist for 2026 covers the broader picture.

What's the difference between paranoia level 1 and 2?

PL1 enables core rules that catch clear-cut attacks with few false positives. PL2 adds stricter checks — more SQL injection patterns, additional header validation, and tighter path handling. PL2 is sensible for a well-known PHP application stack. PL3 and above are generally for high-security environments where staff are available to tune rules regularly.

How do I know if ModSecurity is blocking real attacks vs. false positives?

Check the audit log at /var/log/apache2/modsec_audit.log. Each entry shows the matched data — the actual string that triggered the rule. If the matched data looks like normal form content or URL parameters your app sends, it's a false positive. If it looks like UNION SELECT or <script>alert, it's a real attempt. Use that context to decide whether to add an exclusion or leave the rule as-is.