Configure Elasticsearch 8, Logstash 8, and Kibana 8 to collect, parse, and visualize ModSecurity web application firewall logs from multiple servers for centralized security monitoring and threat detection.
Prerequisites
- Root or sudo access
- 4GB+ RAM recommended
- ModSecurity configured on web servers
- Java 11 compatible system
What this solves
ModSecurity generates detailed logs about web traffic, attacks, and security events, but analyzing these logs across multiple servers becomes challenging without centralization. This tutorial sets up the ELK Stack (Elasticsearch, Logstash, Kibana) to collect ModSecurity logs from your web servers, parse them into structured data, and create dashboards for security monitoring and incident response.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you get the latest versions of all components.
sudo apt update && sudo apt upgrade -y
Install Java 11
All ELK components require Java. Install OpenJDK 11 which provides the best compatibility with Elasticsearch 8.
sudo apt install -y openjdk-11-jdk
Add Elastic repository
Add the official Elastic repository to install Elasticsearch, Logstash, and Kibana from the same source.
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main" | sudo tee /etc/apt/sources.list.d/elastic-8.x.list
sudo apt update
Install Elasticsearch 8
Install Elasticsearch which will store and index your ModSecurity logs for fast searching and analysis.
sudo apt install -y elasticsearch
Configure Elasticsearch
Configure Elasticsearch with appropriate memory settings and network binding for your ModSecurity log analysis setup.
cluster.name: modsecurity-logs
node.name: elk-node-1
path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch
bootstrap.memory_lock: true
network.host: 0.0.0.0
http.port: 9200
discovery.type: single-node
xpack.security.enabled: true
xpack.security.enrollment.enabled: true
xpack.security.http.ssl.enabled: true
xpack.security.http.ssl.keystore.path: certs/http.p12
xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.verification_mode: certificate
xpack.security.transport.ssl.client_authentication: required
xpack.security.transport.ssl.keystore.path: certs/transport.p12
xpack.security.transport.ssl.truststore.path: certs/transport.p12
Set Elasticsearch heap size
Configure JVM heap size to use half of your available RAM. For ModSecurity logs, 2-4GB is typically sufficient for small to medium deployments.
-Xms2g
-Xmx2g
Start and enable Elasticsearch
Start Elasticsearch and enable it to start on boot. The first startup will generate security certificates and passwords.
sudo systemctl daemon-reload
sudo systemctl enable --now elasticsearch
sudo systemctl status elasticsearch
Configure Elasticsearch passwords
Set up the built-in user passwords for secure access. Save the elastic user password as you'll need it for Kibana configuration.
sudo /usr/share/elasticsearch/bin/elasticsearch-setup-passwords auto
Install Logstash 8
Install Logstash to parse and transform ModSecurity logs before sending them to Elasticsearch.
sudo apt install -y logstash
Configure Logstash for ModSecurity logs
Create a Logstash configuration that parses ModSecurity audit logs and extracts security events, attack patterns, and client information.
input {
file {
path => "/var/log/modsec_audit.log"
start_position => "beginning"
type => "modsecurity"
codec => multiline {
pattern => "^--[a-fA-F0-9]{8}-A--$"
negate => true
what => "previous"
}
}
}
filter {
if [type] == "modsecurity" {
grok {
match => {
"message" => "(?--[a-fA-F0-9]{8}-A--\s\[%{HTTPDATE:timestamp}\]\s%{DATA:unique_id}\s%{IPORHOST:client_ip}\s%{INT:client_port}\s%{IPORHOST:server_ip}\s%{INT:server_port})"
}
}
grok {
match => {
"message" => "--[a-fA-F0-9]{8}-B--\s(?[\s\S] ?)(?=--[a-fA-F0-9]{8}-[C-Z]--|$)"
}
}
grok {
match => {
"request_headers" => "%{WORD:http_method}\s+(?\S+)\s+HTTP/%{NUMBER:http_version}"
}
}
grok {
match => {
"message" => "--[a-fA-F0-9]{8}-F--\s(?[\s\S] ?)(?=--[a-fA-F0-9]{8}-[G-Z]--|$)"
}
}
grok {
match => {
"response_headers" => "HTTP/%{NUMBER:response_http_version}\s+(?\d+)"
}
}
grok {
match => {
"message" => "--[a-fA-F0-9]{8}-H--\s(?[\s\S] ?)(?=--[a-fA-F0-9]{8}-[I-Z]--|$)"
}
}
if [audit_messages] {
grok {
match => {
"audit_messages" => "Message: (?.?)\. \[file \"(?. ?)\"\] \[line \"(?\d+)\"\] \[id \"(?\d+)\"\] \[rev \"(?\d+)\"\] \[msg \"(?.?)\"\] \[data \"(?. ?)\"\] \[severity \"(?.?)\"\] \[ver \"(?. ?)\"\] \[maturity \"(?\d+)\"\] \[accuracy \"(?\d+)\"\] \[tag \"(?.*?)\"\]"
}
}
}
date {
match => [ "timestamp", "dd/MMM/yyyy:HH:mm:ss Z" ]
}
mutate {
convert => {
"client_port" => "integer"
"server_port" => "integer"
"response_code" => "integer"
"rule_id" => "integer"
"rule_line" => "integer"
"rule_revision" => "integer"
"maturity" => "integer"
"accuracy" => "integer"
}
}
if [client_ip] {
geoip {
source => "client_ip"
target => "geoip"
}
}
if [rule_tags] {
mutate {
split => { "rule_tags" => "," }
}
}
}
}
output {
elasticsearch {
hosts => ["localhost:9200"]
index => "modsecurity-logs-%{+YYYY.MM.dd}"
user => "elastic"
password => "YOUR_ELASTIC_PASSWORD"
ssl => true
ssl_certificate_verification => false
}
stdout {
codec => rubydebug
}
}
Update Logstash configuration with your password
Replace YOUR_ELASTIC_PASSWORD in the Logstash configuration with the actual elastic user password from the previous setup step.
sudo sed -i 's/YOUR_ELASTIC_PASSWORD/your_actual_password_here/g' /etc/logstash/conf.d/modsecurity.conf
Configure Logstash JVM settings
Set appropriate heap size for Logstash. For ModSecurity log processing, 1-2GB is usually sufficient.
-Xms1g
-Xmx1g
Start and enable Logstash
Start Logstash to begin processing ModSecurity logs and enable it to start on boot.
sudo systemctl enable --now logstash
sudo systemctl status logstash
Install Kibana 8
Install Kibana for creating dashboards and visualizations of your ModSecurity security events.
sudo apt install -y kibana
Configure Kibana
Configure Kibana to connect to Elasticsearch with SSL and proper authentication for secure access to your ModSecurity dashboards.
server.port: 5601
server.host: "0.0.0.0"
server.name: "modsecurity-kibana"
elasticsearch.hosts: ["https://localhost:9200"]
elasticsearch.username: "kibana_system"
elasticsearch.password: "YOUR_KIBANA_SYSTEM_PASSWORD"
elasticsearch.ssl.certificateAuthorities: ["/etc/elasticsearch/certs/http_ca.crt"]
xpack.security.encryptionKey: "something_at_least_32_characters_long_for_session_encryption"
xpack.encryptedSavedObjects.encryptionKey: "something_at_least_32_characters_long_for_saved_objects"
xpack.reporting.encryptionKey: "something_at_least_32_characters_long_for_reporting"
Copy Elasticsearch CA certificate
Copy the Elasticsearch CA certificate so Kibana can verify the SSL connection to Elasticsearch.
sudo cp /etc/elasticsearch/certs/http_ca.crt /etc/kibana/
sudo chown kibana:kibana /etc/kibana/http_ca.crt
sudo chmod 644 /etc/kibana/http_ca.crt
Start and enable Kibana
Start Kibana and enable it to start on boot. Initial startup may take a few minutes as Kibana configures itself.
sudo systemctl enable --now kibana
sudo systemctl status kibana
Configure firewall access
Open the necessary ports for ELK Stack components. Elasticsearch (9200), Logstash (5044), and Kibana (5601).
sudo ufw allow 9200/tcp
sudo ufw allow 5601/tcp
sudo ufw allow 5044/tcp
sudo ufw reload
Configure ModSecurity log forwarding
Set up ModSecurity on your web servers to forward logs to your ELK Stack. This configuration assumes you already have ModSecurity configured with NGINX.
SecAuditEngine On
SecAuditLogType Concurrent
SecAuditLogStorageDir /var/log/modsec_audit
SecAuditLogFormat JSON
SecAuditLogParts ABCFHZ
Set up Filebeat for log shipping
Install Filebeat on your web servers to ship ModSecurity logs to the ELK Stack server.
sudo apt install -y filebeat
Configure Filebeat for ModSecurity
Configure Filebeat to collect ModSecurity logs and send them to your ELK Stack server.
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/modsec_audit.log
fields:
log_type: modsecurity
fields_under_root: true
multiline.pattern: '^--[a-fA-F0-9]{8}-A--$'
multiline.negate: true
multiline.match: after
output.logstash:
hosts: ["YOUR_ELK_SERVER_IP:5044"]
processors:
- add_host_metadata:
when.not.contains.tags: forwarded
logging.level: info
logging.to_files: true
logging.files:
path: /var/log/filebeat
name: filebeat
keepfiles: 7
permissions: 0644
Update Logstash input for Beats
Modify the Logstash configuration to accept logs from Filebeat instead of reading local files.
input {
beats {
port => 5044
}
}
filter {
if [log_type] == "modsecurity" {
# ... (keep the same filter configuration as before)
}
}
output {
elasticsearch {
hosts => ["localhost:9200"]
index => "modsecurity-logs-%{+YYYY.MM.dd}"
user => "elastic"
password => "YOUR_ELASTIC_PASSWORD"
ssl => true
ssl_certificate_verification => false
}
}
Create Kibana index pattern
Access Kibana at http://your-server-ip:5601 and create an index pattern for ModSecurity logs. Use the pattern modsecurity-logs-* and select @timestamp as the time field.
curl -X POST "localhost:5601/api/saved_objects/index-pattern/modsecurity-logs-*" \
-H "Content-Type: application/json" \
-H "kbn-xsrf: true" \
-u "elastic:YOUR_ELASTIC_PASSWORD" \
-d '{
"attributes": {
"title": "modsecurity-logs-*",
"timeFieldName": "@timestamp"
}
}'
Import ModSecurity dashboard
Create a comprehensive dashboard for ModSecurity monitoring with key security metrics and attack visualization.
{
"version": "8.0.0",
"objects": [
{
"id": "modsecurity-overview",
"type": "dashboard",
"attributes": {
"title": "ModSecurity Security Overview",
"hits": 0,
"description": "Overview of ModSecurity security events and threats",
"panelsJSON": "[{\"version\":\"8.0.0\",\"gridData\":{\"x\":0,\"y\":0,\"w\":24,\"h\":15,\"i\":\"1\"},\"panelIndex\":\"1\",\"embeddableConfig\":{},\"panelRefName\":\"panel_1\"}]",
"timeRestore": false,
"version": 1
}
}
]
}
Verify your setup
Test that all ELK Stack components are running and properly configured for ModSecurity log analysis.
# Check all services are running
sudo systemctl status elasticsearch logstash kibana
Test Elasticsearch connectivity
curl -X GET "localhost:9200/_cluster/health?pretty" -u elastic:YOUR_PASSWORD
Check Logstash is processing logs
sudo tail -f /var/log/logstash/logstash-plain.log
Verify Kibana is accessible
curl -I http://localhost:5601
Check if ModSecurity logs are being indexed
curl -X GET "localhost:9200/modsecurity-logs-*/_search?pretty" -u elastic:YOUR_PASSWORD
Generate test ModSecurity event
curl -H "User-Agent: " http://your-web-server/
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Elasticsearch won't start | Insufficient memory or incorrect heap size | Check sudo journalctl -u elasticsearch and adjust heap in /etc/elasticsearch/jvm.options |
| Kibana shows connection refused | Elasticsearch SSL certificate issues | Verify CA certificate path in /etc/kibana/kibana.yml |
| No ModSecurity logs in Kibana | Logstash parsing errors or incorrect input path | Check /var/log/logstash/logstash-plain.log for grok parsing failures |
| Logstash not receiving logs | Filebeat configuration or network connectivity | Test with sudo filebeat test output and check port 5044 connectivity |
| High memory usage | Default settings too high for server capacity | Reduce heap sizes in JVM options files for each component |
| ModSecurity logs not parsed correctly | Log format mismatch with grok patterns | Update grok patterns in Logstash config to match your ModSecurity format |
Next steps
- Scale your ELK Stack with multiple Elasticsearch nodes
- Integrate ClamAV threat detection with your security monitoring
- Configure advanced ModSecurity rules for better threat detection
- Set up Prometheus alerting for ELK Stack monitoring
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Error handling
cleanup() {
echo -e "${RED}Installation failed. Cleaning up...${NC}"
systemctl stop elasticsearch kibana logstash 2>/dev/null || true
systemctl disable elasticsearch kibana logstash 2>/dev/null || true
}
trap cleanup ERR
# Usage
usage() {
echo "Usage: $0 [OPTIONS]"
echo "Options:"
echo " -h, --help Show this help message"
echo "Example: $0"
exit 1
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help) usage ;;
*) echo "Unknown option: $1"; usage ;;
esac
done
# Check if running as root
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root${NC}"
exit 1
fi
echo -e "${GREEN}ELK Stack Installation for ModSecurity Log Analysis${NC}"
echo "=================================================="
# Detect distribution
echo -e "${YELLOW}[1/8] Detecting distribution...${NC}"
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update && apt upgrade -y"
REPO_CMD="apt update"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
REPO_CMD="dnf makecache"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
REPO_CMD="yum makecache"
;;
*)
echo -e "${RED}Unsupported distribution: $ID${NC}"
exit 1
;;
esac
echo -e "${GREEN}Detected: $PRETTY_NAME${NC}"
else
echo -e "${RED}Cannot detect distribution${NC}"
exit 1
fi
# Update system packages
echo -e "${YELLOW}[2/8] Updating system packages...${NC}"
$PKG_UPDATE
# Install Java 11
echo -e "${YELLOW}[3/8] Installing Java 11...${NC}"
if [[ "$PKG_MGR" == "apt" ]]; then
$PKG_INSTALL openjdk-11-jdk
else
$PKG_INSTALL java-11-openjdk java-11-openjdk-devel
fi
# Verify Java installation
java -version
# Add Elastic repository
echo -e "${YELLOW}[4/8] Adding Elastic repository...${NC}"
if [[ "$PKG_MGR" == "apt" ]]; then
# Install prerequisites
$PKG_INSTALL wget gnupg
# Add GPG key and repository
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main" > /etc/apt/sources.list.d/elastic-8.x.list
chmod 644 /etc/apt/sources.list.d/elastic-8.x.list
$REPO_CMD
else
# Install prerequisites
$PKG_INSTALL wget
# Add GPG key and repository
rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch
cat > /etc/yum.repos.d/elasticsearch.repo << 'EOF'
[elasticsearch]
name=Elasticsearch repository for 8.x packages
baseurl=https://artifacts.elastic.co/packages/8.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=0
autorefresh=1
type=rpm-md
EOF
chmod 644 /etc/yum.repos.d/elasticsearch.repo
$REPO_CMD
fi
# Install Elasticsearch
echo -e "${YELLOW}[5/8] Installing Elasticsearch...${NC}"
if [[ "$PKG_MGR" == "apt" ]]; then
$PKG_INSTALL elasticsearch
else
dnf install -y --enablerepo=elasticsearch elasticsearch 2>/dev/null || yum install -y --enablerepo=elasticsearch elasticsearch
fi
# Configure Elasticsearch
echo -e "${YELLOW}[6/8] Configuring Elasticsearch...${NC}"
cp /etc/elasticsearch/elasticsearch.yml /etc/elasticsearch/elasticsearch.yml.backup
cat > /etc/elasticsearch/elasticsearch.yml << EOF
cluster.name: modsecurity-elk
node.name: node-1
path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch
network.host: localhost
http.port: 9200
discovery.type: single-node
xpack.security.enabled: false
xpack.security.enrollment.enabled: false
xpack.security.http.ssl.enabled: false
xpack.security.transport.ssl.enabled: false
EOF
chown root:elasticsearch /etc/elasticsearch/elasticsearch.yml
chmod 660 /etc/elasticsearch/elasticsearch.yml
# Install and configure Kibana
echo -e "${YELLOW}[7/8] Installing and configuring Kibana...${NC}"
if [[ "$PKG_MGR" == "apt" ]]; then
$PKG_INSTALL kibana
else
dnf install -y --enablerepo=elasticsearch kibana 2>/dev/null || yum install -y --enablerepo=elasticsearch kibana
fi
cp /etc/kibana/kibana.yml /etc/kibana/kibana.yml.backup
cat > /etc/kibana/kibana.yml << EOF
server.port: 5601
server.host: "localhost"
elasticsearch.hosts: ["http://localhost:9200"]
EOF
chown root:kibana /etc/kibana/kibana.yml
chmod 660 /etc/kibana/kibana.yml
# Install and configure Logstash
echo -e "${YELLOW}[8/8] Installing and configuring Logstash...${NC}"
if [[ "$PKG_MGR" == "apt" ]]; then
$PKG_INSTALL logstash
else
dnf install -y --enablerepo=elasticsearch logstash 2>/dev/null || yum install -y --enablerepo=elasticsearch logstash
fi
# Create ModSecurity logstash configuration
mkdir -p /etc/logstash/conf.d
cat > /etc/logstash/conf.d/modsecurity.conf << 'EOF'
input {
file {
path => "/var/log/modsec_audit.log"
start_position => "beginning"
codec => multiline {
pattern => "^--[a-fA-F0-9]{8}-A--$"
negate => true
what => "previous"
}
}
}
filter {
if [message] =~ /^--[a-fA-F0-9]{8}-A--$/ {
grok {
match => { "message" => "--(?<audit_id>[a-fA-F0-9]{8})-A--" }
}
mutate {
add_tag => ["modsecurity", "audit_log"]
}
}
if "modsecurity" in [tags] {
grok {
match => {
"message" => "\[(?<timestamp>%{DATA})\] (?<transaction_id>%{DATA}) %{IP:client_ip} %{INT:client_port} %{IP:server_ip} %{INT:server_port}"
}
}
date {
match => [ "timestamp", "dd/MMM/yyyy:HH:mm:ss Z" ]
}
}
}
output {
elasticsearch {
hosts => ["localhost:9200"]
index => "modsecurity-%{+YYYY.MM.dd}"
}
stdout { codec => rubydebug }
}
EOF
chown root:logstash /etc/logstash/conf.d/modsecurity.conf
chmod 644 /etc/logstash/conf.d/modsecurity.conf
# Enable and start services
systemctl daemon-reload
systemctl enable elasticsearch kibana logstash
systemctl start elasticsearch
# Wait for Elasticsearch to start
echo "Waiting for Elasticsearch to start..."
sleep 30
systemctl start kibana logstash
# Configure firewall
if command -v ufw &> /dev/null; then
ufw allow 5601/tcp
elif command -v firewall-cmd &> /dev/null; then
firewall-cmd --permanent --add-port=5601/tcp
firewall-cmd --reload
fi
# Verification
echo -e "${YELLOW}Verifying installation...${NC}"
sleep 10
if systemctl is-active --quiet elasticsearch; then
echo -e "${GREEN}✓ Elasticsearch is running${NC}"
else
echo -e "${RED}✗ Elasticsearch failed to start${NC}"
exit 1
fi
if systemctl is-active --quiet kibana; then
echo -e "${GREEN}✓ Kibana is running${NC}"
else
echo -e "${RED}✗ Kibana failed to start${NC}"
exit 1
fi
if systemctl is-active --quiet logstash; then
echo -e "${GREEN}✓ Logstash is running${NC}"
else
echo -e "${RED}✗ Logstash failed to start${NC}"
exit 1
fi
# Test Elasticsearch
if curl -s "http://localhost:9200" > /dev/null; then
echo -e "${GREEN}✓ Elasticsearch API is accessible${NC}"
else
echo -e "${RED}✗ Elasticsearch API is not accessible${NC}"
exit 1
fi
echo -e "${GREEN}ELK Stack installation completed successfully!${NC}"
echo ""
echo "Next steps:"
echo "1. Access Kibana at: http://localhost:5601"
echo "2. Configure ModSecurity to log to /var/log/modsec_audit.log"
echo "3. Create Kibana dashboards for ModSecurity log analysis"
echo "4. Set up log rotation for ModSecurity logs"
echo ""
echo "Service commands:"
echo " systemctl status elasticsearch"
echo " systemctl status kibana"
echo " systemctl status logstash"
Review the script before running. Execute with: bash install.sh