Qué vas a conseguir
Que los logs de todos los servidores Linux (y los de Windows, con el mismo agente) lleguen a un único sitio donde buscar "permission denied" o un ID de correlación en segundos, con gráficos de cuántos errores hubo y cuándo. Al terminar tendrás la pila mínima en Docker y dos servidores enviando.
Antes de empezar
- El servidor de herramientas de la guía de Docker, con 8 GB de RAM y 100 GB de disco para los índices.
- Puertos abiertos en su firewall: 5044 (Beats → Logstash) desde los servidores, 5601 (Kibana) desde la red de IT.
- Decidir la retención: cuántos días de logs guardas. Treinta es un buen punto de partida.
1. La pila con Compose
services: elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:8.15.3 environment: - discovery.type=single-node - xpack.security.enabled=true - ELASTIC_PASSWORD_FILE=/run/secrets/elastic - ES_JAVA_OPTS=-Xms2g -Xmx2g volumes: [es-data:/usr/share/elasticsearch/data] secrets: [elastic] ulimits: { memlock: { soft: -1, hard: -1 } } logstash: image: docker.elastic.co/logstash/logstash:8.15.3 volumes: [./pipeline:/usr/share/logstash/pipeline:ro] ports: ["5044:5044"] depends_on: [elasticsearch] kibana: image: docker.elastic.co/kibana/kibana:8.15.3 ports: ["5601:5601"] environment: - ELASTICSEARCH_HOSTS=http://elasticsearch:9200 depends_on: [elasticsearch]volumes: es-data:secrets: elastic: file: ./secrets/elastic.txtsudo sysctl -w vm.max_map_count=262144 && echo "vm.max_map_count=262144" | sudo tee /etc/sysctl.d/99-elastic.confdocker compose up -d && sleep 60curl -s -u elastic:$(cat secrets/elastic.txt) http://localhost:9200/_cluster/health?pretty | grep -E 'status|number_of_nodes' "status" : "green", "number_of_nodes" : 1,Un solo nodo y status: green es lo normal para un entorno de IT interno. Kibana necesita su propia cuenta de servicio: docker compose exec elasticsearch bin/elasticsearch-reset-password -u kibana_system y la pones en la variable ELASTICSEARCH_PASSWORD de Kibana.
2. El pipeline de Logstash
input { beats { port => 5044 }}filter { if [event][dataset] == "system.syslog" { grok { match => { "message" => "%{SYSLOGTIMESTAMP:ts} %{SYSLOGHOST:host.name} %{DATA:process.name}(?:\[%{POSINT:process.pid}\])?: %{GREEDYDATA:log.message}" } } date { match => ["ts", "MMM d HH:mm:ss", "MMM dd HH:mm:ss"] } mutate { remove_field => ["ts"] } } if "Permission denied" in [message] or "Failed password" in [message] { mutate { add_tag => ["security"] } }}output { elasticsearch { hosts => ["http://elasticsearch:9200"] user => "elastic" password => "${ELASTIC_PASSWORD}" index => "logs-linux-%{+YYYY.MM.dd}" }}El grok convierte una línea de texto en campos (host.name, process.name, log.message): sin eso solo podrías buscar texto libre. La etiqueta security es el primer ejemplo de algo que luego usarás en una alerta. Comprueba la sintaxis antes de arrancar: docker compose exec logstash bin/logstash --config.test_and_exit -f /usr/share/logstash/pipeline/syslog.conf.
3. Filebeat en los servidores
curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-8.15.3-x86_64.rpm && sudo rpm -vi filebeat-8.15.3-x86_64.rpmsudo filebeat modules enable systemsudo sed -i 's|#output.logstash:|output.logstash:|; s|#hosts: \["localhost:5044"\]|hosts: ["srv-tools-01.contoso.local:5044"]|' /etc/filebeat/filebeat.ymlsudo sed -i 's|^output.elasticsearch:|#output.elasticsearch:|; s|^ hosts: \["localhost:9200"\]|# hosts: ["localhost:9200"]|' /etc/filebeat/filebeat.ymlsudo filebeat test outputlogstash: srv-tools-01.contoso.local:5044... connection... parse host... OK dns lookup... OK TCP... OK talk to server... OKsudo systemctl enable --now filebeatfilebeat test output es la prueba que haces antes de abrir un ticket al equipo de red: si TCP... OK falla, es el firewall; si falla talk to server, es Logstash. Con Ansible (guía anterior), este paso es un playbook de diez líneas para toda la flota.
4. Buscar en Kibana
Para que Kibana vea los índices hace falta una Data view (Stack Management → Data views → Create, patrón logs-linux-*, campo de tiempo @timestamp). A partir de ahí: la misma búsqueda en todos los servidores a la vez, el histograma arriba te dice a qué hora empezó el problema, y Visualize sobre process.name te enseña qué servicio genera más errores.
5. Retención y espacio
curl -s -u elastic:$(cat secrets/elastic.txt) -X PUT http://localhost:9200/_ilm/policy/logs-30d -H 'Content-Type: application/json' -d '{"policy":{"phases":{"hot":{"actions":{}},"delete":{"min_age":"30d","actions":{"delete":{}}}}}}'{"acknowledged":true}curl -s -u elastic:$(cat secrets/elastic.txt) -X PUT http://localhost:9200/_index_template/logs-linux -H 'Content-Type: application/json' -d '{"index_patterns":["logs-linux-*"],"template":{"settings":{"index.lifecycle.name":"logs-30d","number_of_replicas":0}}}'{"acknowledged":true}curl -s -u elastic:$(cat secrets/elastic.txt) 'http://localhost:9200/_cat/indices/logs-*?v&h=index,docs.count,store.size&s=index' | tail -3logs-linux-2026.10.20 412033 211.4mblogs-linux-2026.10.21 398870 204.9mblogs-linux-2026.10.22 88121 46.1mbCon un solo nodo, number_of_replicas: 0 evita que el clúster esté siempre en amarillo. Unos 200 MB al día por veinte servidores es el orden de magnitud; mira _cat/indices al mes para ajustar la retención al disco que tienes.
Siguientes pasos
- Windows: el agente es Winlogbeat con el mismo
output.logstash; los eventos de seguridad (4625, 4740) llegan ya con campos. - Una alerta en Kibana (
Alerts and Insights → Rules) sobretags: securitycon más de 20 eventos en 5 minutos, enviada al canal del equipo. - Si el volumen crece, la opción es Elastic Agent + Fleet en lugar de Beats sueltos, o pasar los dashboards a Grafana (siguiente guía) con Elasticsearch como origen de datos.
¿Falta algo o en tu entorno es distinto? Escríbeme y lo corrijo.
What you'll get
Logs from every Linux server (and Windows, with the same agent) arriving in one place where you can search for "permission denied" or a correlation ID in seconds, with charts of how many errors there were and when. By the end you will have the minimal stack in Docker and two servers shipping.
Before you start
- The tools server from the Docker guide, with 8 GB of RAM and 100 GB of disk for the indices.
- Ports open in its firewall: 5044 (Beats → Logstash) from the servers, 5601 (Kibana) from the IT network.
- Decide the retention: how many days of logs you keep. Thirty is a good starting point.
1. The stack with Compose
services: elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:8.15.3 environment: - discovery.type=single-node - xpack.security.enabled=true - ELASTIC_PASSWORD_FILE=/run/secrets/elastic - ES_JAVA_OPTS=-Xms2g -Xmx2g volumes: [es-data:/usr/share/elasticsearch/data] secrets: [elastic] ulimits: { memlock: { soft: -1, hard: -1 } } logstash: image: docker.elastic.co/logstash/logstash:8.15.3 volumes: [./pipeline:/usr/share/logstash/pipeline:ro] ports: ["5044:5044"] depends_on: [elasticsearch] kibana: image: docker.elastic.co/kibana/kibana:8.15.3 ports: ["5601:5601"] environment: - ELASTICSEARCH_HOSTS=http://elasticsearch:9200 depends_on: [elasticsearch]volumes: es-data:secrets: elastic: file: ./secrets/elastic.txtsudo sysctl -w vm.max_map_count=262144 && echo "vm.max_map_count=262144" | sudo tee /etc/sysctl.d/99-elastic.confdocker compose up -d && sleep 60curl -s -u elastic:$(cat secrets/elastic.txt) http://localhost:9200/_cluster/health?pretty | grep -E 'status|number_of_nodes' "status" : "green", "number_of_nodes" : 1,One node and status: green is normal for an internal IT environment. Kibana needs its own service account: docker compose exec elasticsearch bin/elasticsearch-reset-password -u kibana_system, and you put it in Kibana's ELASTICSEARCH_PASSWORD variable.
2. The Logstash pipeline
input { beats { port => 5044 }}filter { if [event][dataset] == "system.syslog" { grok { match => { "message" => "%{SYSLOGTIMESTAMP:ts} %{SYSLOGHOST:host.name} %{DATA:process.name}(?:\[%{POSINT:process.pid}\])?: %{GREEDYDATA:log.message}" } } date { match => ["ts", "MMM d HH:mm:ss", "MMM dd HH:mm:ss"] } mutate { remove_field => ["ts"] } } if "Permission denied" in [message] or "Failed password" in [message] { mutate { add_tag => ["security"] } }}output { elasticsearch { hosts => ["http://elasticsearch:9200"] user => "elastic" password => "${ELASTIC_PASSWORD}" index => "logs-linux-%{+YYYY.MM.dd}" }}The grok turns a line of text into fields (host.name, process.name, log.message): without it you could only search free text. The security tag is the first example of something you will later use in an alert. Check the syntax before starting: docker compose exec logstash bin/logstash --config.test_and_exit -f /usr/share/logstash/pipeline/syslog.conf.
3. Filebeat on the servers
curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-8.15.3-x86_64.rpm && sudo rpm -vi filebeat-8.15.3-x86_64.rpmsudo filebeat modules enable systemsudo sed -i 's|#output.logstash:|output.logstash:|; s|#hosts: \["localhost:5044"\]|hosts: ["srv-tools-01.contoso.local:5044"]|' /etc/filebeat/filebeat.ymlsudo sed -i 's|^output.elasticsearch:|#output.elasticsearch:|; s|^ hosts: \["localhost:9200"\]|# hosts: ["localhost:9200"]|' /etc/filebeat/filebeat.ymlsudo filebeat test outputlogstash: srv-tools-01.contoso.local:5044... connection... parse host... OK dns lookup... OK TCP... OK talk to server... OKsudo systemctl enable --now filebeatfilebeat test output is the test you run before opening a ticket with the network team: if TCP... OK fails, it is the firewall; if talk to server fails, it is Logstash. With Ansible (previous guide), this step is a ten-line playbook for the whole fleet.
4. Searching in Kibana
For Kibana to see the indices you need a Data view (Stack Management → Data views → Create, pattern logs-linux-*, time field @timestamp). From there: the same search across every server at once, the histogram at the top tells you what time the problem started, and Visualize on process.name shows which service produces the most errors.
5. Retention and disk
curl -s -u elastic:$(cat secrets/elastic.txt) -X PUT http://localhost:9200/_ilm/policy/logs-30d -H 'Content-Type: application/json' -d '{"policy":{"phases":{"hot":{"actions":{}},"delete":{"min_age":"30d","actions":{"delete":{}}}}}}'{"acknowledged":true}curl -s -u elastic:$(cat secrets/elastic.txt) -X PUT http://localhost:9200/_index_template/logs-linux -H 'Content-Type: application/json' -d '{"index_patterns":["logs-linux-*"],"template":{"settings":{"index.lifecycle.name":"logs-30d","number_of_replicas":0}}}'{"acknowledged":true}curl -s -u elastic:$(cat secrets/elastic.txt) 'http://localhost:9200/_cat/indices/logs-*?v&h=index,docs.count,store.size&s=index' | tail -3logs-linux-2026.10.20 412033 211.4mblogs-linux-2026.10.21 398870 204.9mblogs-linux-2026.10.22 88121 46.1mbWith a single node, number_of_replicas: 0 stops the cluster from being permanently yellow. Around 200 MB a day for twenty servers is the order of magnitude; look at _cat/indices after a month to fit the retention to the disk you have.
Next steps
- Windows: the agent is Winlogbeat with the same
output.logstash; security events (4625, 4740) arrive with fields already parsed. - An alert in Kibana (
Alerts and Insights → Rules) ontags: securitywith more than 20 events in 5 minutes, sent to the team channel. - If volume grows, the option is Elastic Agent + Fleet instead of standalone Beats, or moving the dashboards to Grafana (next guide) with Elasticsearch as a data source.
Is something missing, or different in your environment? Get in touch and I'll fix it.
Tech Career Lab · newsletter
¿Te ha servido? Cada dos semanas envío una idea práctica para perfiles IT: LinkedIn, proyectos, networking y procesos de selección.Found this useful? Every two weeks I send one practical idea for IT professionals: LinkedIn, projects, networking and hiring processes.
Apúntate y llévate las 33 ideasSign up and get the 33 ideas →