Self-Defending Reverse Proxy Setup

Transform raw Nginx Proxy Manager logs into a proactive defense system using Wazuh.

Self-Defending Reverse Proxy Setup

The deployment of Nginx Proxy Manager (NPM) is often accompanied by a sobering realization: the access logs are immediately flooded with noise. Bots, scanners, and script kiddies relentlessly hammer the gateway, looking for vulnerabilities. Instead of letting these logs rot in a file, they can be transformed into a proactive defense mechanism. By combining Wazuh’s log analysis capabilities with IP reputation lists and Active Response, a self-defending architecture is created that automatically bans malicious actors.

audio-thumbnail
Podcast self defending npm
0:00
/317.650958

Architecture Overview

The concept relies on a streamlined flow of information. The Wazuh Agent, running on the Docker host where NPM resides, reads the raw Nginx logs and forwards them to the Wazuh Manager.

On the Manager side, the heavy lifting occurs. The logs are decoded and compared against a set of custom rules. These rules detect specific patterns—such as 400 Bad Requests, hex-encoded payloads, or known malicious IPs. Once a threat threshold is crossed (e.g., a severity level of 10 or higher), the Active Response module triggers, instructing the Agent to drop the attacker's IP address immediately using iptables or nftables.

I should note that these ideas were originally proposed by a colleague I recently hired.

Onboarding a New Colleague - But He is Not Human
See how an n8n-based AI can implement complex Wazuh rules to detect new attack patterns.
My AI Colleague’s New Rules of Engagement
After its initial training, the AI assistant gets more responsibility, governed by a new Prime Directive.
Wazuh on Steroids: AI-Powered Incident Analysis On-Demand
Upgrade Wazuh with an AI-powered active response for on-demand, deep-dive incident analysis.

Agent Configuration for Log Ingestion

Location: Agent (Docker Host) - File: /var/ossec/etc/ossec.conf

The first step ensures the Agent is actually watching the correct files. Since Wazuh possesses a robust internal decoder for Nginx logs that matches the Apache format, the configuration is set accordingly. The path must be adjusted to match the actual volume location of the Nginx Proxy Manager logs on the host system.

<localfile>
  <location>/var/docker/web/nginx-proxy-manager/data/logs/*.log</location>
  <log_format>apache</log_format>
</localfile>

After saving the configuration, the agent requires a restart to apply the changes:
systemctl restart wazuh-agent

Custom Detection Rules

Location: Manager - File: /var/ossec/etc/rules/local_rules.xml

Standard rules often miss the nuances of a reverse proxy environment. Therefore, a custom rule set is defined to handle specific threats targeting NPM. These rules are grouped to detect bad requests, hex scanners, and exploit attempts.

The logic is built hierarchically:
  1. Rule 100200: Captures all web logs.
  2. Rule 100204: Filters specifically for HTTP Status 400 (Bad Request).
  3. High Severity Rules: If a 400 error contains specific patterns (like \x for hex, or cgi-bin for exploits), the severity is raised to 12.
  4. Frequency Rule: If a single IP generates ten 400-errors within 60 seconds, it is flagged (Level 10).
<group name="npm,accesslogs,">

  <!-- 1. Base Rule: Capture all Web Logs -->
  <rule id="100200" level="3">
    <decoded_as>web-accesslog</decoded_as>
    <description>All NPM access log events</description>
  </rule>

  <!-- 2. Status 400 (Bad Request) as a basis for attacks -->
  <rule id="100204" level="6">
    <if_sid>100200</if_sid>
    <field name="http_status_code">400</field>
    <description>Bad request (400) detected</description>
  </rule>

  <!-- 3. Attack: Hex Scanners, SIP, SMB Junk (Level 12 -> BLOCK) -->
  <rule id="100205" level="12">
    <if_sid>100204</if_sid>
    <regex>\\x|SIP/|RTSP/|SMB|PROPFIND</regex>
    <description>NPM: Malicious Binary/Protocol Scanner detected</description>
    <mitre><id>T1190</id></mitre>
    <group>active_response_trigger,attack</group>
  </rule>

  <!-- 4. Attack: Exploit Attempts & Path Traversal (Level 12 -> BLOCK) -->
  <rule id="100206" level="12">
    <if_sid>100204</if_sid>
    <regex>cgi-bin|/bin/sh|%2e|%2E|\.\./</regex>
    <description>NPM: Web Exploit/Path Traversal attempt detected</description>
    <mitre><id>T1210</id></mitre>
    <group>active_response_trigger,attack</group>
  </rule>

  <!-- 5. Attack: Too many 400 errors (10x in 60s) (Level 10 -> BLOCK) -->
  <rule id="100207" level="10" frequency="10" timeframe="60">
    <if_matched_sid>100204</if_matched_sid>
    <same_source_ip />
    <description>NPM: Multiple Bad Requests (400) from same IP</description>
    <group>active_response_trigger,service_scan</group>
  </rule>

  <!-- 6. Threat Intel: Check against Malicious IP List (Level 12 -> BLOCK) -->
  <rule id="100300" level="12">
    <if_sid>100200</if_sid>
    <list field="srcip" lookup="address_match_key">etc/lists/malicious-ioc/malicious-ip</list>
    <description>Access from known malicious IP detected</description>
    <group>malicious_ip,active_response_trigger</group>
  </rule>

</group>
Automatische Incident-Analyse mit Wazuh
Rohe Wazuh-Alarme in vollständige Analysen verwandeln - mit einer lokalen KI und Ntfy-Push-Nachrichten.

Implementing CDB Lists for IP Reputation

Location: Manager

Wazuh uses Constant Database (CDB) files for fast lookups. A common pitfall here is the "chicken and egg" problem: the configuration will fail if the file does not exist, but the file cannot be compiled if the configuration is invalid.

A. Configuration

The list is registered in the ossec.conf of the Manager, specifically within the <ruleset> block. A relative path is used here.

<list>etc/lists/malicious-ioc/malicious-ip</list>
B. Initial Creation

To avoid startup errors, a dummy file is created, permissions are corrected, and the manager is restarted to force the initial .cdb compilation.

# Create directory
mkdir -p /var/ossec/etc/lists/malicious-ioc

# Create dummy file
echo "1.1.1.1:malicious" > /var/ossec/etc/lists/malicious-ioc/malicious-ip

# Set permissions
chown -R wazuh:wazuh /var/ossec/etc/lists/malicious-ioc/
chmod 660 /var/ossec/etc/lists/malicious-ioc/malicious-ip

# Restart forces compilation
systemctl restart wazuh-manager

Automating Threat Intelligence Updates

Location: Manager - File: /var/ossec/bin/update_malicious_ips.sh

A static blacklist is about as useful as a screen door on a submarine. To keep the defense effective, a script is implemented to fetch fresh data (e.g., from GreenSnow), format it for Wazuh, and reload the database.

The following script handles the download and formatting (appending :malicious to every IP). There are lots of iplists to choose from (see https://iplists.firehol.org/):

#!/bin/bash
# Script by M. Meister
LIST_PATH="/var/ossec/etc/lists/malicious-ioc"
IP_FILE="$LIST_PATH/malicious-ip"
TEMP_FILE="/tmp/malicious_ips.txt"

# 1. Download
curl -s "https://blocklist.greensnow.co/greensnow.txt" > $TEMP_FILE

# 2. Format (IP:malicious) and Move
if [ -s $TEMP_FILE ]; then
    sed -i 's/$/:malicious/' $TEMP_FILE
    mv $TEMP_FILE $IP_FILE
    chown wazuh:wazuh $IP_FILE
    chmod 660 $IP_FILE
    
    # 3. Reload (Triggers compilation via wazuh-analysisd)
    if command -v systemctl &> /dev/null; then
        systemctl reload wazuh-manager
    else
        /var/ossec/bin/wazuh-control reload
    fi
    echo "List updated successfully."
fi

The script is made executable (chmod +x ...) and scheduled via crontab -e to run daily, for example at 4:00 AM:
0 4 * * * /var/ossec/bin/update_malicious_ips.sh > /dev/null 2>&1

Activating the Ban Hammer

Location: Manager - File: /var/ossec/etc/ossec.conf

Finally, the detection must be linked to an action. The Active Response block dictates that any alert of level 10 or higher results in a temporary firewall ban.

<active-response>
  <command>firewall-drop</command>
  <location>local</location>
  <level>10</level>
  <timeout>600</timeout> <!-- Block duration in seconds -->
</active-response>

A final restart of the manager (systemctl restart wazuh-manager) arms the system.

Conclusion

By implementing this pipeline, the Nginx Proxy Manager is no longer a passive victim of internet noise. Attacks are now identified by specific error patterns or reputation checks and immediately blocked by the firewall. It even reduces the SIEM-server footprint, since there is no need for an external Graylog server for log enrichment. The result is a cleaner log file and a significantly reduced attack surface, allowing administrators to sleep a little sounder while the server defends itself.