
Even if you already have a good SIEM (Security Information and Event Management) running in your infrastructure, you need to know about Wazuh. This was a great discovery for me, because you can personalize Wazuh as your main security monitor in a big infrastructure, or set Wazuh to be a great tool on your own home network.
Wazuh is a must-have service because it’s easy to install and the default configuration is already quite complete. But if you have need a custom set up, the level of configuration could supply any system stack.
What is Wazuh?
Wazuh, an open-source SIEM tool, provides a solution for threat detection, intrusion detection, vulnerability detection, log analysis, and the best of all, it teaches you how to prevent these issues. In this post, we are going to see the process of installing Wazuh on an Ubuntu Server using Docker, making the implementation seamless and efficient. I will also highlight a few points so you can move this service to your environment.
Why Wazuh?
Wazuh offers a scalable and flexible approach to security monitoring, making it an ideal choice for organizations of all sizes. By centralizing and analyzing security data from diverse sources, Wazuh empowers organizations to proactively respond to potential threats and vulnerabilities.
Prerequisites
Before we dive into the installation process, ensure that you have the following prerequisites in place:
- Linux Server: Ensure you have a clean installation, I’m going to use Ubuntu Server with Docker and Docker Compose .
- Wazuh documentation, you will need it for custom configuration
Note: I highly recommend following the step-by-step guide if you are going to set up a Wazuh in a production environment. In this case, this is a home lab, so I’m following the quick-start guide.
Important Note: For Telegram alerting, follow the instruction in the Telegram section, if you don’t mind about that, continue with the steps above.
Installation process
Single-node deployment
Clone the Wazuh repository to your server, then we are going to create or copy the certificates and change the default password:
$ git clone https://github.com/wazuh/wazuh-docker.git
You will need to provide a group of certificates for each node in the stack to secure communication between the nodes. You have two alternatives to provide these certificates:
Generate self-signed certificates for each cluster node.
We have created a Docker image to automate certificate generation using the Wazuh certs gen tool.
# Wazuh App Copyright (C) 2021 Wazuh Inc. (License GPLv2)
version: '3'
services:
generator:
image: wazuh/wazuh-certs-generator:0.0.1
hostname: wazuh-certs-generator
volumes:
- ./config/wazuh_indexer_ssl_certs/:/certificates/
- ./config/certs.yml:/config/certs.yml
environment:
- HTTP_PROXY=YOUR_PROXY_ADDRESS_OR_DNS
Execute the following command to get the desired certificates:
$ docker-compose -f generate-indexer-certs.yml run --rm generator
This saves the certificates into the config/wazuh_indexer_ssl_certs
directory.
Provide your own certificates for each node.
In case you have your own certificates, provision them as follows in the config/wazuh_indexer_ssl_certs
directory:
Indexer
config/wazuh_indexer_ssl_certs/root-ca.pem
config/wazuh_indexer_ssl_certs/wazuh.indexer-key.pem configwazuh_indexer_ssl_certs/wazuh.indexer.pem
config/wazuh_indexer_ssl_certs/admin.pem
config/wazuh_indexer_ssl_certs/admin-key.pem
Manager
config/wazuh_indexer_ssl_certs/root-ca-manager.pem
config/wazuh_indexer_ssl_certs/wazuh.manager.pem
config/wazuh_indexer_ssl_certs/wazuh.manager-key.pem
Dashboard
config/wazuh_indexer_ssl_certs/wazuh.dashboard.pem
config/wazuh_indexer_ssl_certs/wazuh.dashboard-key.pem
config/wazuh_indexer_ssl_certs/root-ca.pem
Start the Wazuh single-node deployment using docker-compose
:
$ docker-compose up -d
Debugging
To know when the Wazuh indexer is up, the Wazuh dashboard container uses curl
to run multiple queries to the Wazuh indexer API. You can expect to see several Failed to connect to Wazuh indexer port 9200
log messages or “Wazuh dashboard server is not ready yet ” until the Wazuh indexer is started. Then the setup process continues normally. It takes about 1 minute for the Wazuh indexer to start up. You can find the default Wazuh indexer credentials in the docker-compose.yml
file.
The default username and password for the Wazuh dashboard are admin
and SecretPassword
. Do not forget to change the default password for the Wazuh indexer admin user.
Change the password of Wazuh users
To improve security, you can change the default password of the Wazuh users. There are two types of users:
- Wazuh indexer users
- Wazuh API users
To change the password of these Wazuh users, perform the following steps. You must run the commands from your
single-node
directory, depending on your Wazuh on Docker deployment.
Wazuh indexer users
To change the password of the default
admin
andkibanaserver
users, do the following. You can only change one at a time.
Setting a new hash
Stop the deployment stack if it’s running:
$ docker-compose down
Run this command to generate the hash of your new password. Once the container launches, input the new password and press Enter.
$ docker run --rm -ti wazuh/wazuh-indexer:4.7.2 bash /usr/share/wazuh-indexer/plugins/opensearch-security/tools/hash.sh
Copy the generated hash and open the config/wazuh_indexer/internal_users.yml
file. Locate the block for the user you are changing password for.
Replace the hash.
admin
user
...
admin:
hash: "$2y$12$K/SpwjtB.wOHJ/Nc6GVRDuc1h0rM1DfvziFRNPtk27P.c4yDr9njO"
reserved: true
backend_roles:
- "admin"
description: "Demo admin user"
...
kibanaserver
user
...
kibanaserver:
hash: "$2a$12$4AcgAt3xwOWadA5s5blL6ev39OXDNhmOesEoo33eZtrq2N0YrU3H."
reserved: true
description: "Demo kibanaserver user"
...
Setting the new password
Open the docker-compose.yml
file. Change all occurrences of the old password with the new one. For example, for a single-node deployment:
admin
user
...
services:
wazuh.manager:
environment:
- INDEXER_URL=https://wazuh.indexer:9200
- INDEXER_USERNAME=admin
- INDEXER_PASSWORD=SecretPassword
- FILEBEAT_SSL_VERIFICATION_MODE=full
- SSL_CERTIFICATE_AUTHORITIES=/etc/ssl/root-ca.pem
- SSL_CERTIFICATE=/etc/ssl/filebeat.pem
- SSL_KEY=/etc/ssl/filebeat.key
- API_USERNAME=wazuh-wui
- API_PASSWORD=MyS3cr37P450r.*-
wazuh.dashboard:
environment:
- INDEXER_USERNAME=admin
- INDEXER_PASSWORD=SecretPassword
- WAZUH_API_URL=https://wazuh.manager
- DASHBOARD_USERNAME=kibanaserver
- DASHBOARD_PASSWORD=kibanaserver
- API_USERNAME=wazuh-wui
- API_PASSWORD=MyS3cr37P450r.*-
...
kibanaserver
user
...
services:
wazuh.dashboard:
environment:
- INDEXER_USERNAME=admin
- INDEXER_PASSWORD=SecretPassword
- WAZUH_API_URL=https://wazuh.manager
- DASHBOARD_USERNAME=kibanaserver
- DASHBOARD_PASSWORD=kibanaserver
- API_USERNAME=wazuh-wui
- API_PASSWORD=MyS3cr37P450r.*-
...
Applying the changes
Start the deployment stack.
$ docker-compose up -d
Run docker ps
and note the name of the first Wazuh indexer container. For example, single-node-wazuh.indexer-1
, or multi-node-wazuh1.indexer-1
.
Run docker exec -it <WAZUH_INDEXER_CONTAINER_NAME> bash
to enter the container. For example:
$ docker exec -it single-node-wazuh.indexer-1 bash
Set the following variables:
export INSTALLATION_DIR=/usr/share/wazuh-indexer
CACERT=$INSTALLATION_DIR/certs/root-ca.pem
KEY=$INSTALLATION_DIR/certs/admin-key.pem
CERT=$INSTALLATION_DIR/certs/admin.pem
export JAVA_HOME=/usr/share/wazuh-indexer/jdk
Wait for the Wazuh indexer to initialize properly. The waiting time can vary from two to five minutes. It depends on the size of the cluster, the assigned resources, and the speed of the network. Then, run the securityadmin.sh
script to apply all changes.
$ bash /usr/share/wazuh-indexer/plugins/opensearch-security/tools/securityadmin.sh -cd /usr/share/wazuh-indexer/opensearch-security/ -nhnv -cacert $CACERT -cert $CERT -key $KEY -p 9200 -icl
Exit the Wazuh indexer container and login with the new credentials on the Wazuh dashboard.
Checking the installation
List the containers in the directory where the Wazuh docker-compose.yml
file is located:
$ docker-compose ps
You should get something like this
Name Command State Ports
--------------------------------------------------------------------------------------------------------------------------------------------------------------
single-node_wazuh.dashboard_1 /entrypoint.sh Up 443/tcp, 0.0.0.0:443->5601/tcp,:::443->5601/tcp
single-node_wazuh.indexer_1 /entrypoint.sh opensearchw ... Up 0.0.0.0:9200->9200/tcp,:::9200->9200/tcp
single-node_wazuh.manager_1 /init Up 0.0.0.0:1514->1514/tcp,:::1514->1514/tcp,0.0.0.0:1515->1515/tcp,:::1515->1515/tcp,1516/tcp,0.0.0.0:514->514/udp,:::514->514/udp, 0.0.0.0:55000->55000/tcp,:::55000->55000/tcp
Access Wazuh Manager
Once the container is running, you can access the Wazuh manager’s web interface by opening a web browser and navigating to https://<your-server-ip> or this could be
, the port depends on your compose configuration: https://<your-server-ip>
:<port>
...
wazuh.dashboard:
image: wazuh/wazuh-dashboard:4.7.1
hostname: wazuh.dashboard
restart: always
ports:
- 443:5601
...
Log in to start configuring and monitoring your security events.

Using Wazuh
Once you login, you’ll find a dashboard like this:

Add agents
We are going to start adding agents to later test all the features Wazuh have. Go to the agent sections and click on “Deploy new agent”.

This part is really straightforward, first select your OS

In server address, add the IP/FQDN of the Wazuh server

Then add a name to identify the endpoint, and if you want, you can make groups to have better organization

In the end, you are going to see a command you have to run on your endpoint. In the case of windows, open the PowerShell as admin and copy the command.
Windows



Linux

Click close, go back to agents, and after a minute, you will see your agent connected and running:

All right, let’s click in one agent to see what’s going on in the endpoint. On mati-win11, an agent running on Windows 11, we get a lot of information. First we have our ID that identify the agent on the infrastructure, also the status, IP, version of Wazuh agent, group, OS, node and registration/last discovery date. Above this information, we can find all the security assessment.

Quick tour in every Dashboard component
The Mitre tactics framework is a knowledge base and model for cyber adversary behavior, reflecting the various phases of an adversary’s attack lifecycle and the platforms they are known to target. More in MITRE ATT&CK and the Enterprise tactics.

Wazuh helps implement compliance requirements for regulatory compliance support and visibility. This is done by providing automation, improved security controls, log analysis, and incident response.
The default Wazuh ruleset provides support for PCI DSS, HIPAA, NIST 800-53, TSC, and GDPR frameworks and standards. Wazuh rules and decoders are used to detect attacks, system errors, security misconfigurations, and policy violations. More information about compliance here.

Each Wazuh agent has its own local database where it stores the current state of each SCA (Security Configuration Assessment). The Wazuh server maintains an SCA database for all agents that are enrolled to it. Wazuh agents only send the differences detected between scans to the Wazuh server. If there has been no change, only a summary of the SCA scan is sent, in this way unnecessary network traffic is avoided while keeping the SCA database on the Wazuh server up to date. The Wazuh server then uses those updates to issue alerts that are shown in the Wazuh dashboard.


Clicking on Ubuntu server CIS
, we can see every test CIS do. This check, for example, is about chony, the time-server. As we can see, The task is about “Ensure chrony is enabled and running“, the result is “fail“. Above we get a Rationale, a description about why it is important to fix this issue, and more impressive (for me at least) a Remediation description to know how to solve it. How cool is that? New in security? In 20 minutes you have a professional tool to know more about your flaws and more important, learn how to solve it!

Testing Wazuh: Attempt SSH login
Let go back, and click on “Security events“:

Here is another dashboard with all the events on this specific node. As we can see, I have “0 Authentication failures“. Let’s fail some ssh logins to see whats happend with Wazuh


After failing ssh attempts:


Take a closer look to “Attempt to login using a non-existent user“. Here, you can see the ssh attempt with “testuser” on the Ubuntu server. With all the details of this event. As I mentioned in the beginning of this post, I don’t configure anything, this is a default rule for ssh connections.

CVE: Common Vulnerabilities and Exposures
By default Wazuh brings CVE detection disable, let’s enabled the CVE so we also know how to make configuration on the endpoints:
In this case we have to remember that we are using Docker, so the configuration is different than the default one. For configuration without docker, follow the official documentation.
In this case, I’m using a asingle node configuration. If you need you can have a multi-node configuration (bigger systems). Go to the docker directory where you install wazuh and follow this path: ./wazuh-docker/single-node/config/wazuh_cluster/wazuh_manager.conf
. Here we are going to enable the vulnerability scan. Change the following lines and set enable for your differents OS endpoints:
<vulnerability-detector>
<enabled>yes</enabled>
<interval>5m</interval>
<min_full_scan_interval>6h</min_full_scan_interval>
<run_on_start>yes</run_on_start>
<!-- Ubuntu OS vulnerabilities -->
<provider name="canonical">
<enabled>yes</enabled>
<os>trusty</os>
<os>xenial</os>
<os>bionic</os>
<os>focal</os>
<os>jammy</os>
<update_interval>1h</update_interval>
</provider>
<!-- Debian OS vulnerabilities -->
<provider name="debian">
<enabled>yes</enabled>
<os>buster</os>
<os>bullseye</os>
<os>bookworm</os>
<update_interval>1h</update_interval>
</provider>
<!-- RedHat OS vulnerabilities -->
<provider name="redhat">
<enabled>yes</enabled>
<os>5</os>
<os>6</os>
<os>7</os>
<os>8</os>
<os>9</os>
<update_interval>1h</update_interval>
</provider>
<!-- Windows OS vulnerabilities -->
<provider name="msu">
<enabled>yes</enabled>
<update_interval>1h</update_interval>
</provider>
<!-- Aggregate vulnerabilities -->
<provider name="nvd">
<enabled>yes</enabled>
<update_interval>1h</update_interval>
</provider>
</vulnerability-detector>
Remember to restart the agent once you complete the changes:
$ docker-compose stop
$ docker-compose up -d
Sometimes after the restart, you may notice the dashboard is not fully available, the first vulnerability scan uses CPU power to collect the first data, so be patience.

When wazuh is up again, go to Agents -> Vulneratibilities, and you will find a report for each agent you have. Let’s see the report on the Windows 11 OS:

Here is a Critical Vulnerability on the VLC installation:

Checking the CVE that affect VLC on NVD:


As the NVD web says, we need to update to 3.0.20 to patch this vulneratility, let’s check VLC:



All right! Vulneraty fixed 😎.

Telegram Alerts
To get the alerts on telegram we need to create a docker image first. Luckly, Wazuh is one step head and the repository comes with a script to create a new docker images.
Creating a Docker image
After clonning the repository, modify the Dockerfile
on ./wazuh-docker/build-docker-images/wazuh-manager/Dockerfile
$ git clone https://github.com/wazuh/wazuh-docker.git
# Wazuh Docker Copyright (C) 2017, Wazuh Inc. (License GPLv2)
FROM ubuntu:jammy
RUN rm /bin/sh && ln -s /bin/bash /bin/sh
ARG WAZUH_VERSION
ARG WAZUH_TAG_REVISION
ARG FILEBEAT_TEMPLATE_BRANCH
ARG FILEBEAT_CHANNEL=filebeat-oss
ARG FILEBEAT_VERSION=7.10.2
ARG WAZUH_FILEBEAT_MODULE
RUN apt-get update && apt install curl apt-transport-https lsb-release xz-utils gnupg pip -y
RUN pip3 install requests
[...]
Note at the beginning of the file, we are going to add pip
package to the instalation, and the create a new task to install requests
using pip. Pip is the package installer for Python, and requests is a HTML library.
Now let’s build this wazuh images (this post could be old, so check out to the version of wazuh you need):
$ build-docker-images/build-images.sh -v 4.8.0
This step take time (7 minutes ETC, in my home server), so after a while, you should check your new images using:
$ docker image ls | grep -i wazuh
wazuh/wazuh-dashboard 4.8.0 d29e3fe9cc0e 4 hours ago 1.03GB
wazuh/wazuh-indexer 4.8.0 f37a435e9399 4 hours ago 2.25GB
wazuh/wazuh-manager 4.8.0 0ab64de94f51 4 hours ago 11GB
Provide your own certificates for each node.
In case you have your own certificates, provision them as follows in the config/wazuh_indexer_ssl_certs
directory:
Indexer
config/wazuh_indexer_ssl_certs/root-ca.pem
config/wazuh_indexer_ssl_certs/wazuh.indexer-key.pem configwazuh_indexer_ssl_certs/wazuh.indexer.pem
config/wazuh_indexer_ssl_certs/admin.pem
config/wazuh_indexer_ssl_certs/admin-key.pem
Manager
config/wazuh_indexer_ssl_certs/root-ca-manager.pem
config/wazuh_indexer_ssl_certs/wazuh.manager.pem
config/wazuh_indexer_ssl_certs/wazuh.manager-key.pem
Dashboard
config/wazuh_indexer_ssl_certs/wazuh.dashboard.pem
config/wazuh_indexer_ssl_certs/wazuh.dashboard-key.pem
config/wazuh_indexer_ssl_certs/root-ca.pem
Generate self-signed certificates for each cluster node.
With the images ready, next step will be create the certificates. I have problems with the default configuration, so I will show you what I did. Move to ./wazuh-docker/single-node/config/wazuh_indexer_ssl_certs
and delete every folder:
$ sudo rm -rf ./wazuh-docker/single-node/config/wazuh_indexer_ssl_certs/*
Now increase max_map_count on your host. This command must be run with root permissions:
# sysctl -w vm.max_map_count=262144
Run the .yml
to generate the certificates, that in the ./wazuh-docker/single-node directory
and will fill with the new certs the wazuh_indexer_ssl_certs
.
$ docker-compose -f generate-indexer-certs.yml run --rm generator
Create a bot on telegram
To send Wazuh alerts to a Telegram chat, we need to create a bot. We have to send a couple of messages to @BotFather. After starting the bot with the /start
command, we have to send the /newbot
command to start creating the bot, and we will choose the name of the bot, wazuh_mm_bot in this case.

We can also set a profile picture:

Now chat with the bot and write /start

Once the bot is ready, we need the chat id
, this is the identifier of the conversation we are having with the bot. To get the chat id
we have to access this webpage:
https://api.telegram.org/bot<YOUR-BOT-TOKEN>/getUpdates
{"ok":true,"result":[{"update_id":530302469,"message":{"message_id":27,"from":{"id":38488931,"is_bot":false,"first_name":"xxxxxx","last_name":"xxxxxx","username":"xxxxxx" ,"language_code":"es"},"chat":{"id":xxxxxxxxx,"first_name":"xxxxxxx","last_name":"xxxxxxx","username":"xxxxxxx","type":"p rivate"},"date":1595488212,"text":"a"}}]}
With the chat id
and the api token
, we are ready for the script.
Python Script
The adition of requests
we made previously on the docker image will help us in the following script. We have to set the interpreter to be used by the script:
#!/usr/bin/env python3
The script will have three arguments:
- alert_file: file containing the alert.
- hook_url: defined in the
ossec.conf
, contains the token.
alert_file = open(sys.argv[1])
hook_url = sys.argv[3]
Now the function to generate the message, remember to set your chat_id:
def create_message(alert_json):
# Get alert information
title = alert_json['rule']['description'] if 'description' in alert_json['rule'] else ''
description = alert_json['full_log'] if 'full_log' in alert_json else ''
description.replace("\\n", "\n")
alert_level = alert_json['rule']['level'] if 'level' in alert_json['rule'] else ''
groups = ', '.join(alert_json['rule']['groups']) if 'groups' in alert_json['rule'] else ''
rule_id = alert_json['rule']['id'] if 'rule' in alert_json else ''
agent_name = alert_json['agent']['name'] if 'name' in alert_json['agent'] else ''
agent_id = alert_json['agent']['id'] if 'id' in alert_json['agent'] else ''
# Format message with markdown
msg_content = f'*{title}*\n\n'
msg_content += f'_{description}_\n'
msg_content += f'*Groups:* {groups}\n' if len(groups) > 0 else ''
msg_content += f'*Rule:* {rule_id} (Level {alert_level})\n'
msg_content += f'*Agent:* {agent_name} ({agent_id})\n' if len(agent_name) > 0 else ''
msg_data = {}
msg_data['chat_id'] = CHAT_ID
msg_data['text'] = msg_content
msg_data['parse_mode'] = 'markdown'
# Debug information
with open('/var/ossec/logs/integrations.log', 'a') as f:
f.write(f'MSG: {msg_data}\n')
return json.dumps(msg_data)
In this case, messages will have the following information:
- title: description of the alert, if it exists.
- description: complete log of the alert.
- groups: groups of the rule.
- rule: rule identifier and its level.
- agent: agent’s name and identifier.

You can also modify the information sent by the script by adding fields from the alerts in the alerts.json
file.
The message is written in markdown format, we can play with the format of the message that will be sent. With the alert format done, we can send it to Telegram using:
msg_data = create_message(alert_json)
headers = {'content-type': 'application/json', 'Accept-Charset': 'UTF-8'}
response = requests.post(hook_url, headers=headers, data=msg_data)
Add some debug message in case there is any problem with the script:
with open('/var/ossec/logs/integrations.log', 'a') as f:
f.write(f'MSG: {msg_data}\n')
with open('/var/ossec/logs/integrations.log', 'a') as f:
f.write(f'RESPONSE: {response}\n')
The final script
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import json
try:
import requests
except Exception:
print("No module 'requests' found. Install: pip3 install requests")
sys.exit(1)
CHAT_ID = "xxxxxxxx"
def create_message(alert_json):
# Get alert information
title = alert_json['rule']['description'] if 'description' in alert_json['rule'] else ''
description = alert_json['full_log'] if 'full_log' in alert_json else ''
description.replace("\\n", "\n")
alert_level = alert_json['rule']['level'] if 'level' in alert_json['rule'] else ''
groups = ', '.join(alert_json['rule']['groups']) if 'groups' in alert_json['rule'] else ''
rule_id = alert_json['rule']['id'] if 'rule' in alert_json else ''
agent_name = alert_json['agent']['name'] if 'name' in alert_json['agent'] else ''
agent_id = alert_json['agent']['id'] if 'id' in alert_json['agent'] else ''
# Format message with markdown
msg_content = f'*{title}*\n\n'
msg_content += f'_{description}_\n'
msg_content += f'*Groups:* {groups}\n' if len(groups) > 0 else ''
msg_content += f'*Rule:* {rule_id} (Level {alert_level})\n'
msg_content += f'*Agent:* {agent_name} ({agent_id})\n' if len(agent_name) > 0 else ''
msg_data = {}
msg_data['chat_id'] = CHAT_ID
msg_data['text'] = msg_content
msg_data['parse_mode'] = 'markdown'
# Debug information
with open('/var/ossec/logs/integrations.log', 'a') as f:
f.write(f'MSG: {msg_data}\n')
return json.dumps(msg_data)
# Read configuration parameters
alert_file = open(sys.argv[1])
hook_url = sys.argv[3]
# Read the alert file
alert_json = json.loads(alert_file.read())
alert_file.close()
# Send the request
msg_data = create_message(alert_json)
headers = {'content-type': 'application/json', 'Accept-Charset': 'UTF-8'}
response = requests.post(hook_url, headers=headers, data=msg_data)
# Debug information
with open('/var/ossec/logs/integrations.log', 'a') as f:
f.write(f'RESPONSE: {response}\n')
sys.exit(0)
Save the script as custom-telegram
. You can change the name, but remember that it must start with “custom-“. More information about custom integration here
Configuring Wazuh to send alerts to Telegram
Let’s edit ./wazuh-docker/single-node/config/wazuh_cluster/wazuh_manager.conf.
This is the famous “ossec.conf
” file that is been using inside the container, it’s linked to our local server using “volumes” in the docker-compose.yml
[...]
<!-- Telegram bot -->
<integration>
<name>custom-telegram</name>
<hook_url>https://api.telegram.org/bot<BOT-API-HERE>/sendMessage</hook_url>
<alert_format>json</alert_format>
</integration>
</ossec_config>
[...]
I didn’t knowed where to add this instegration block, so I add it almost at the end of the file, inside <ossec_config>
.
Create a new directory on ./wazuh-docker/single-node
, and copy the script inside. Then, give the following permissions and ownership:
$ mkdir single-node/integrations
$ mv custom-telegram single-node/integrations
$ sudo chown -R root:root single-node/integrations
$ sudo chmod -R 750 single-node/integrations
Edit the docker-compose.yml
file to match the same image you previously build, and add the integration folder to the volumes
# Wazuh App Copyright (C) 2017, Wazuh Inc. (License GPLv2)
version: '3.7'
services:
wazuh.manager:
image: wazuh/wazuh-manager:4.8.0
hostname: wazuh.manager
restart: always
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 655360
hard: 655360
ports:
- "1514:1514"
- "1515:1515"
- "514:514/udp"
- "55000:55000"
environment:
- INDEXER_URL=https://wazuh.indexer:9200
- INDEXER_USERNAME=admin
- INDEXER_PASSWORD=ChangetoEnvPass
- FILEBEAT_SSL_VERIFICATION_MODE=full
- SSL_CERTIFICATE_AUTHORITIES=/etc/ssl/root-ca.pem
- SSL_CERTIFICATE=/etc/ssl/filebeat.pem
- SSL_KEY=/etc/ssl/filebeat.key
- API_USERNAME=wazuh-wui
- API_PASSWORD=APIPASS.*-
volumes:
- wazuh_api_configuration:/var/ossec/api/configuration
- wazuh_etc:/var/ossec/etc
- wazuh_logs:/var/ossec/logs
- wazuh_queue:/var/ossec/queue
- wazuh_var_multigroups:/var/ossec/var/multigroups
- wazuh_active_response:/var/ossec/active-response/bin
- wazuh_agentless:/var/ossec/agentless
- wazuh_wodles:/var/ossec/wodles
- filebeat_etc:/etc/filebeat
- filebeat_var:/var/lib/filebeat
- /home/user/wazuh-docker/single-node/integrations:/var/ossec/integrations
- ./config/wazuh_indexer_ssl_certs/root-ca-manager.pem:/etc/ssl/root-ca.pem
- ./config/wazuh_indexer_ssl_certs/wazuh.manager.pem:/etc/ssl/filebeat.pem
- ./config/wazuh_indexer_ssl_certs/wazuh.manager-key.pem:/etc/ssl/filebeat.key
- ./config/wazuh_cluster/wazuh_manager.conf:/wazuh-config-mount/etc/ossec.conf
wazuh.indexer:
image: wazuh/wazuh-indexer:4.8.0
hostname: wazuh.indexer
restart: always
ports:
- "9200:9200"
environment:
- "OPENSEARCH_JAVA_OPTS=-Xms1024m -Xmx1024m"
- 'INDEXER_PASSWORD=ChangetoEnvPass'
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 65536
hard: 65536
volumes:
- wazuh-indexer-data:/var/lib/wazuh-indexer
- ./config/wazuh_indexer_ssl_certs/root-ca.pem:/usr/share/wazuh-indexer/certs/root-ca.pem
- ./config/wazuh_indexer_ssl_certs/wazuh.indexer-key.pem:/usr/share/wazuh-indexer/certs/wazuh.indexer.key
- ./config/wazuh_indexer_ssl_certs/wazuh.indexer.pem:/usr/share/wazuh-indexer/certs/wazuh.indexer.pem
- ./config/wazuh_indexer_ssl_certs/admin.pem:/usr/share/wazuh-indexer/certs/admin.pem
- ./config/wazuh_indexer_ssl_certs/admin-key.pem:/usr/share/wazuh-indexer/certs/admin-key.pem
- ./config/wazuh_indexer/wazuh.indexer.yml:/usr/share/wazuh-indexer/opensearch.yml
- ./config/wazuh_indexer/internal_users.yml:/usr/share/wazuh-indexer/opensearch-security/internal_users.yml
wazuh.dashboard:
image: wazuh/wazuh-dashboard:4.8.0
hostname: wazuh.dashboard
restart: always
ports:
- 443:5601
environment:
- INDEXER_USERNAME=admin
- INDEXER_PASSWORD=ChangetoEnvPass
- WAZUH_API_URL=https://wazuh.manager
- DASHBOARD_USERNAME=kibanaserver
- DASHBOARD_PASSWORD=kibanaserver
- API_USERNAME=wazuh-wui
- API_PASSWORD=APIPASS.*-
volumes:
- ./config/wazuh_indexer_ssl_certs/wazuh.dashboard.pem:/usr/share/wazuh-dashboard/certs/wazuh-dashboard.pem
- ./config/wazuh_indexer_ssl_certs/wazuh.dashboard-key.pem:/usr/share/wazuh-dashboard/certs/wazuh-dashboard-key.pem
- ./config/wazuh_indexer_ssl_certs/root-ca.pem:/usr/share/wazuh-dashboard/certs/root-ca.pem
- ./config/wazuh_dashboard/opensearch_dashboards.yml:/usr/share/wazuh-dashboard/config/opensearch_dashboards.yml
- ./config/wazuh_dashboard/wazuh.yml:/usr/share/wazuh-dashboard/data/wazuh/config/wazuh.yml
- wazuh-dashboard-config:/usr/share/wazuh-dashboard/data/wazuh/config
- wazuh-dashboard-custom:/usr/share/wazuh-dashboard/plugins/wazuh/public/assets/custom
depends_on:
- wazuh.indexer
links:
- wazuh.indexer:wazuh.indexer
- wazuh.manager:wazuh.manager
volumes:
wazuh_api_configuration:
wazuh_etc:
wazuh_logs:
wazuh_queue:
wazuh_var_multigroups:
wazuh_active_response:
wazuh_agentless:
wazuh_wodles:
filebeat_etc:
filebeat_var:
wazuh-indexer-data:
wazuh-dashboard-config:
wazuh-dashboard-custom:
Final step, light a candle 🕯️ and run the docker-compose.yml
:
$ docker-compose up -d
I test the alerts trying to login via ssh using a fake user: testuserTelegram

Troubleshoot note
If the bot isn’t working, enter the container to check the logs:
$ docker ps
acd4bd78jef6 wazuh/wazuh-manager:4.8.0 "/init" 2 hours ago Up 2 hours 0.0.0.0:1514-1515->1514-1515/tcp, :::1514-1515->1514-1515/tcp, 0.0.0.0:5
$ docker exec -it acd4bd78jef6 bash
root@wazuh:/# cat /var/log/ossec.log | grep -i telegram | tail
if you have a message like this:
2024/02/14 01:28:06 wazuh-integratord: ERROR: Couldn't execute command (integrations /tmp/custom-telegram-1707874086--958114520.alert https://api.telegram.or g/botYOUR-API-KEY-HERE/sendMessage 10 3 > /dev/null 2>&1). Check file and permissions.
Change the ownership of the integrations directory inside the container:
root@wazuh:/# chown -R root:wazuh /var/ossec/integrations
By installing Wazuh, you’ve taken a significant step toward reinforcing your organization’s cybersecurity defenses. Wazuh’s powerful features and ease of integration make it an invaluable tool for monitoring and responding to security threats effectively. Continue to explore Wazuh’s capabilities, i will see you in the next post.
References
- https://medium.com/@jesusjimsa_12801
- https://pypi.org/project/requests/
- https://documentation.wazuh.com/current/index.html