The Problem: Syslog Was Never Designed for a SIEM
Firewalls, switches, routers, VPNs, printers, and dozens of other infrastructure devices speak one universal language for log output: syslog. It is simple, ubiquitous, and decades old. That simplicity is both its strength and its fundamental weakness when you try to use it with a modern SIEM like Wazuh.
Native Wazuh Capabilities
Wazuh is designed as an all-encompassing security platform, featuring native mechanisms for agent-less monitoring and direct syslog ingestion. These capabilities provide immediate network visibility, allowing the platform to ingest telemetry and verify compliance on infrastructure devices, like firewalls, core switches, and edge routers, where deploying a full endpoint agent is technically unfeasible. Yet, while this out-of-the-box flexibility is convenient for initial setup, relying on native ingestion creates several challenges.
The Agent-0 Problem
Wazuh is an agent-based platform. Every event in the system is tied to an agent — a registered identity with a name, ID, group membership, and compliance profile. When a device sends syslog directly to the Wazuh manager’s built-in syslog listener (port 514), Wazuh has no agent to associate the event with. It assigns these events to agent 0, which is the internal identifier for the manager itself and all unregistered sources.
The consequences are significant:
- Loss of device identity. All events from all unregistered devices are merged into a single stream. The only way to distinguish sources is by IP address in the log message — a field that is not indexed as a first-class entity.
- No agent-level rules or groups. Wazuh’s powerful agent group system — which lets you apply different rule sets, SCA policies, and compliance checks per device type — is completely unavailable for agent-0 events.
- No agent health monitoring. You cannot alert on a device going silent, because there is no registered agent to track as disconnected.
- Dashboard pollution. Agent-0 events appear under the manager’s own identity in the Wazuh dashboard, making it impossible to separate infrastructure noise from endpoint activity at a glance.
Syslog Is Unencrypted by Default
Standard syslog over UDP or plain TCP transmits log data in cleartext. On any network path between the originating device and the SIEM, an observer can read every log line — including authentication events, VPN session details, and firewall rule hits. For organizations with compliance requirements (PCI-DSS, ISO 27001, SOC 2), transmitting security-relevant logs in cleartext is a finding waiting to be detected.
No Reliability Guarantees
Syslog over UDP, the most common transport, offers no delivery confirmation. Under load — exactly when you need your logs most, during an incident — UDP syslog is silently dropped. Neither the sender nor the receiver knows a message was lost. TCP syslog improves this, but provides no application-level acknowledgment that the SIEM actually processed the event.
No Flow Control or Buffering
If the SIEM is temporarily unavailable — during an upgrade, a restart, or an outage — devices keep sending syslog into the void. There is no queue, no retry, no buffering on the sender side. Those logs are gone.
Authentication Is Absent
Any host on the network that can reach port 514 can inject arbitrary syslog messages. There is no way to verify that a message claiming to come from corp-firewall-01 actually came from that device (apart from the IP which might be written into the full log).
The Solution: A Syslog-to-Wazuh-Agent Bridge
Instead of sending syslog directly to the Wazuh manager, we introduce a lightweight intermediate layer: a Docker container that receives syslog, stores it locally, and forwards it to Wazuh via a proper registered agent.
[Firewall / Switch / Router]
|
| syslog UDP/TCP
v
+-----------------------------------------------+
| docker-wazuh-syslog-forwarder |
| |
| rsyslog --> /var/log/remote/<host>/<date> |
| | |
| wazuh-agent (registered) |
+-----------------------------------------------+
|
| TLS-encrypted, authenticated
v
[Wazuh Manager]
This architecture solves every problem described above:
| Problem | Solution |
|---|---|
| Agent-0 events | Container registers as a named agent; all events carry its identity |
| No encryption | Wazuh agent uses TLS to the manager |
| No delivery guarantee | Agent protocol has acknowledgment and retry |
| No buffering | Logs are written to a Docker volume; agent reads from disk |
| No authentication | Agent enrollment uses a shared secret or certificate |
| Can’t apply groups/rules | Agent can be placed in any Wazuh group at enrollment time |
Multiple containers can run in parallel, one per network segment, each registering as a distinct agent. A DMZ forwarder and a corporate LAN forwarder appear as separate, independently managed agents in the Wazuh dashboard.
Step-by-Step Tutorial
Prerequisites
- Docker Engine 24+ and Docker Compose v2.2+
- A running Wazuh manager (4.x) with agent ports 1514 (communication) and 1515 (enrollment) reachable
- Git
- Python 3 (for the test script — no external packages required)
1. Clone the Repository
git clone https://github.com/isecng/docker-wazuh-syslog-forwarder.git
cd docker-wazuh-syslog-forwarder
The repository structure is as follows:
.
├── config/
│ ├── rsyslog.conf # rsyslog main config
│ └── rsyslog.d/
│ └── remote.conf # syslog receiver and file routing
│ └── ossec.conf # Wazuh agent config template
├── instances/
│ └── example/ # template for new instances
│ ├── .env
│ └── rsyslog.d/
│ └── remote.conf
├── test/
│ ├── send_syslog.py # RFC 3164 test sender
│ ├── decoder_test_syslog.xml # Wazuh decoder for test messages
│ └── rules_test_syslog.xml # Wazuh rules for test messages
├── docker-compose.yml
├── Dockerfile
├── entrypoint.sh
└── spawn.sh # multi-instance management wrapper
2. Configure the Shared Environment
Copy the example file and fill in your Wazuh manager details:
cp .env.example .env
Edit .env:
# Wazuh version - must match the running manager
WAZUH_VERSION=4.14.5
# Manager connection
WAZUH_MANAGER=wazuh.example.com
WAZUH_MANAGER_PORT=1514
WAZUH_PROTOCOL=tcp
# Enrollment
WAZUH_REGISTRATION_PORT=1515
WAZUH_REGISTRATION_PASSWORD=your-enrollment-password
The agent version must match your manager’s major and minor version. To check your manager version:
docker exec <manager-container> /var/ossec/bin/wazuh-control info | grep version
3. Build the Image
docker compose build
The Dockerfile installs rsyslog and the Wazuh agent at the pinned version. The agent package installation requires WAZUH_MANAGER to be set — the build uses a placeholder and the actual address is injected at container startup via entrypoint.sh.
4. Deploy the Forwarder
Choose one of the two deployment options below. They are mutually exclusive — use Option A for a simple single-segment setup, or Option B if you need to collect logs from multiple separate network zones.
Option A - Single Instance
The simplest case: one forwarder, one agent, one syslog port.
docker compose up -d
docker compose logs -f syslog-forwarder
On first start, the Wazuh agent auto-enrolls with the manager. You will see output similar to:
syslog-forwarder | Starting Wazuh agent...
syslog-forwarder | Started wazuh-agentd...
syslog-forwarder | Starting rsyslog...
After ~30 seconds, the agent appears in the Wazuh dashboard as Active. Continue at step 5.
Option B - Multiple Instances in Parallel
Use this when you want to receive syslog from different network segments on separate ports, with each segment appearing as its own named agent in Wazuh. Each container gets a unique port, agent name, and isolated log volume.
Create one directory per instance under instances/:
# Instance for the DMZ segment
cp -r instances/example instances/dmz
cat > instances/dmz/.env << 'EOF'
SYSLOG_PORT=5514
WAZUH_AGENT_NAME=syslog-dmz
WAZUH_AGENT_GROUP=network-devices,dmz
EOF
# Instance for the corporate LAN
cp -r instances/example instances/corp
cat > instances/corp/.env << 'EOF'
SYSLOG_PORT=5515
WAZUH_AGENT_NAME=syslog-corp
WAZUH_AGENT_GROUP=network-devices,corp
EOF
Start all instances at once with the spawn.sh wrapper:
./spawn.sh all up -d
Or manage them individually:
./spawn.sh dmz up -d
./spawn.sh dmz logs -f
./spawn.sh corp up -d
spawn.sh sets a unique Docker Compose project name per instance (syslog-dmz, syslog-corp), giving each its own isolated volume and container namespace:
syslog-dmz_syslog_remote_logs
syslog-corp_syslog_remote_logs
Each instance enrolls as a separate Wazuh agent and can be assigned to different agent groups for independent rule sets and dashboards.
5. Point Your Devices at the Forwarder
Configure syslog on your network devices to send to the Docker host’s IP on the port assigned to the appropriate instance.
Example for a Cisco IOS device targeting the DMZ instance:
logging host 192.168.1.10 transport udp port 5514
logging trap informational
Example for a FortiGate targeting the same instance:
config log syslogd setting
set status enable
set server 192.168.1.10
set port 5514
set mode udp
end
6. Deploy the Test Decoder and Rules
The repository ships a Wazuh decoder and rule set for messages produced by the test script in the next step. Deploy them to the manager before running the test so that events are decoded and generate alerts immediately.
Decoders and rules run on the manager, not the agent:
docker cp test/decoder_test_syslog.xml <manager-container>:/var/ossec/etc/decoders/
docker cp test/rules_test_syslog.xml <manager-container>:/var/ossec/etc/rules/
docker exec <manager-container> /var/ossec/bin/wazuh-control restart
After the manager restarts, test messages will be decoded and generate alerts at level 3, making them visible in the alerts index immediately when you run step 7.
7. Test with the Included Test Script
The repository includes a test syslog sender that requires no external Python packages:
# Send 3 UDP messages pretending to be a firewall
python3 test/send_syslog.py --count 3 --hostname demo-firewall
Output:
[UDP] -> 127.0.0.1:5514 <134>May 05 12:31:30 demo-firewall test: Test syslog message from send_syslog.py [1/3]
[UDP] -> 127.0.0.1:5514 <134>May 05 12:31:30 demo-firewall test: Test syslog message from send_syslog.py [2/3]
[UDP] -> 127.0.0.1:5514 <134>May 05 12:31:30 demo-firewall test: Test syslog message from send_syslog.py [3/3]
Verify the log file was created inside the container:
docker compose exec syslog-forwarder find /var/log/remote -type f
# /var/log/remote/demo-firewall/2026-05-05.log
docker compose exec syslog-forwarder cat /var/log/remote/demo-firewall/2026-05-05.log
# May 5 12:31:30 demo-firewall test: Test syslog message from send_syslog.py [1/3]
# May 5 12:31:30 demo-firewall test: Test syslog message from send_syslog.py [2/3]
# May 5 12:31:30 demo-firewall test: Test syslog message from send_syslog.py [3/3]
Logs are organized as /var/log/remote/<HOSTNAME>/<YYYY-MM-DD>.log. The Wazuh agent picks up new hostname directories within ~60 seconds using its glob pattern — no restart required.
8. Verify Events in Wazuh
In the Wazuh dashboard, go to Security Events and filter:
agent.name: syslog-dmz
Events from demo-firewall appear under the syslog-dmz agent identity — not agent 0. Because you deployed the decoder and rules in step 6 before sending the test messages, they will have been decoded with the correct fields and appear as level-3 alerts in the alerts index.
How It Works Under the Hood
rsyslog: Receiving and Storing
The rsyslog configuration listens on both UDP and TCP port 514 inside the container and writes every incoming message to a dynamic file path based on the sender’s hostname:
module(load="imudp")
module(load="imtcp")
input(type="imudp" port="514")
input(type="imtcp" port="514")
template(name="RemoteHostLog" type="string"
string="/var/log/remote/%HOSTNAME%/%$YEAR%-%$MONTH%-%$DAY%.log")
if $fromhost-ip != '127.0.0.1' then {
action(type="omfile" DynaFile="RemoteHostLog"
dirCreateMode="0755" fileCreateMode="0644"
dirOwner="syslog" fileOwner="syslog")
stop
}
The %HOSTNAME% field comes from the RFC 3164 syslog header sent by the device. This means your firewall’s logs land in /var/log/remote/corp-fw-01/ and your switch’s logs land in /var/log/remote/core-sw-01/ — automatically, with no configuration per device.
Wazuh Agent: Forwarding to the Manager
The agent configuration monitors the log directory using a glob pattern:
<localfile>
<log_format>syslog</log_format>
<location>/var/log/remote/*/*</location>
</localfile>
This pattern matches files two levels deep: <hostname>/<date>.log. New directories (new devices) are picked up automatically on the next scan cycle.
All variables — manager address, enrollment password, agent name, group membership — are injected at container startup by entrypoint.sh via sed substitution into the config template. No configuration is baked into the image, making the same image reusable across environments.
Persistent Volume: No Log Loss
The log directory is a named Docker volume. If the container restarts the logs are still there and the agent resumes reading from where it left off. This provides the buffering that raw syslog UDP completely lacks.
Links
- Repository: https://github.com/iSecNG/docker-wazuh-syslog-forwarder
- Wazuh: https://wazuh.com
- Wazuh Ambassadors Program: https://wazuh.com/ambassadors-program
Licensing
This project is licensed under the Business Source License 1.1. Use is free for individuals, non-profits, NGOs, and iSecNG customers. Commercial use by competing security monitoring or SIEM service providers requires a separate license. Contact sales@isecng.de.
The license automatically converts to Apache 2.0 on 2030-05-05.