Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Centralized Logging with the ELK Stack

By Kokil Thapa | Last reviewed: August 2026

Debugging production issues across multiple servers by SSH-ing into individual boxes and grepping text files is unsustainable for any growing web application. Implementing centralized logging with the ELK Stack solves this by aggregating logs from your Laravel applications, Nginx web servers, and system services into a single searchable interface. For developers managing complex architectures like those described in my modern Laravel architecture best practices guide, moving to a unified observability platform is often the turning point between reactive firefighting and proactive monitoring.

How does centralized logging with the ELK Stack actually work?

The ELK Stack (now officially called the Elastic Stack) consists of four distinct components that form a reliable log shipping pipeline. Understanding the specific role of each component prevents the most common architectural mistake: trying to send logs directly from your application to Elasticsearch. In production, you always want a buffering and parsing layer.

  • Beats (Filebeat): Lightweight shippers installed on each source server. They read log files, track registry state to prevent duplicates, and forward data reliably even during network blips.
  • Logstash: The processing engine. It receives raw log streams, applies filters (grok patterns, date parsing, geoip), enriches data, and handles backpressure buffering before indexing.
  • Elasticsearch: A distributed search and analytics engine optimized for log data. It stores structured JSON documents in time-based indices and provides near real-time search capabilities.
  • Kibana: The visualization layer. It connects to Elasticsearch to provide dashboards, discovery interfaces, and alerting configuration without requiring raw query knowledge.
Source ServersLaravel AppNginx / PHP-FPMSystem LogsLogstashParse & FilterBuffer QueueEnrich DataElasticsearchIndex & StoreSearch EngineTime-Series DataKibanaVisualize & Alert
Centralized logging with the ELK Stack flows through four stages: collection, processing, storage, and visualization

This separation of concerns matters because Elasticsearch is optimized for indexing and searching, not for handling bursty TCP connections from hundreds of application servers. Logstash acts as a shock absorber. On projects where I've managed legal-tech portals with unpredictable traffic spikes during Nepali court filing seasons, this buffering layer has prevented log loss during peak loads when direct-to-Elasticsearch setups would have dropped events.

How do you configure Filebeat for Laravel structured logs?

Laravel 12 writes logs in either single-line or daily rotation mode by default. For centralized logging, switch to the daily channel with structured JSON output. Unstructured text logs require expensive grok parsing at ingest time; JSON logs can be indexed directly with minimal transformation.

Configure Laravel for JSON logging

In your config/logging.php, set up a dedicated channel for production that outputs valid JSON:

<?php
// config/logging.php
'channels' => [
    'production_json' => [
        'driver' => 'daily',
        'path' => storage_path('logs/laravel.json'),
        'formatter' => Monolog\Formatter\JsonFormatter::class,
        'formatter_with' => [
            'includeStacktraces' => true,
        ],
        'level' => 'info',
        'days' => 14,
    ],
],

Set LOG_CHANNEL=production_json in your production .env. The JsonFormatter ensures every log entry is a single valid JSON object with timestamp, level, message, context, and extra fields properly escaped. This eliminates multiline log parsing headaches entirely.

Install and configure Filebeat

On Ubuntu 24.04 servers running PHP 8.4 and Laravel 12, install Filebeat from the official Elastic repository rather than Ubuntu's default packages to get the current 8.x release:

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 && sudo apt install filebeat

Create a dedicated input configuration at /etc/filebeat/conf.d/laravel.yml:

filebeat.inputs:
- type: filestream
  id: laravel-json-logs
  enabled: true
  paths:
    - /var/www/html/storage/logs/laravel-*.json
  parsers:
    - ndjson:
        target: ""
        add_error_key: true
        overwrite_keys: true
  fields:
    app: laravel
    environment: production
  fields_under_root: true

output.logstash:
  hosts: ["logstash.internal:5044"]
  ssl.certificate_authorities: ["/etc/filebeat/certs/ca.crt"]
  ssl.certificate: "/etc/filebeat/certs/filebeat.crt"
  ssl.key: "/etc/filebeat/certs/filebeat.key"

The ndjson parser tells Filebeat to treat each line as a complete JSON document and merge its fields into the event root. Setting target: "" avoids nesting everything under a json key, which simplifies Kibana queries later. Always enable TLS between Filebeat and Logstash in production; log data frequently contains PII, session tokens, or user identifiers that must not traverse the network in plaintext. For teams building secure systems, this aligns with the authentication patterns discussed in guides on building secure authentication systems.

What Logstash pipeline configuration handles high-volume ingestion?

Logstash pipelines are defined in /etc/logstash/conf.d/. A common mistake is writing monolithic pipeline configs that become unmaintainable. Split your pipeline into discrete stages and use conditional processing based on the app field set by Filebeat.

# /etc/logstash/conf.d/01-input-beats.conf
input {
  beats {
    port => 5044
    ssl_enabled => true
    ssl_certificate => "/etc/logstash/certs/logstash.crt"
    ssl_key => "/etc/logstash/certs/logstash.key"
    ssl_certificate_authorities => ["/etc/logstash/certs/ca.crt"]
  }
}

# /etc/logstash/conf.d/02-filter-laravel.conf
filter {
  if [app] == "laravel" {
    date {
      match => ["datetime", "ISO8601"]
      target => "@timestamp"
    }
    
    mutate {
      rename => { "message" => "log.message" }
      rename => { "level_name" => "log.level" }
      rename => { "context.exception.class" => "error.type" }
      rename => { "context.exception.message" => "error.message" }
      rename => { "context.exception.trace" => "error.stack_trace" }
    }
    
    if [context][user_id] {
      mutate {
        rename => { "[context][user_id]" => "user.id" }
      }
    }
  }
}

# /etc/logstash/conf.d/99-output-elasticsearch.conf
output {
  elasticsearch {
    hosts => ["https://es-node-1:9200", "https://es-node-2:9200"]
    index => "logs-%{[app]}-%{+YYYY.MM.dd}"
    user => "${ES_USER}"
    password => "${ES_PASSWORD}"
    ssl_certificate_authorities => "/etc/logstash/certs/ca.crt"
  }
}

The index pattern logs-laravel-2026.08.17 creates daily indices that integrate naturally with Elasticsearch's Index Lifecycle Management (ILM). Daily indices allow you to delete old data efficiently without expensive delete-by-query operations. On eCommerce projects handling thousands of orders per day, I've found that keeping 30 days of hot logs on SSD-backed nodes and rolling older data to cheaper storage via ILM policies keeps infrastructure costs predictable.

Beats InputPort 5044TLS EnabledBatch ReceiveFilter StageDate ParseField RenameConditional LogicGeoIP LookupElasticsearchDaily IndicesILM PolicyReplica ShardsDead LetterFailed EventsDLQ EnabledAlert on Error
Logstash pipeline processes events through discrete filter stages before indexing to Elasticsearch or routing failures to DLQ

Always enable the Dead Letter Queue (DLQ) in logstash.yml by setting dead_letter_queue.enable: true. When a malformed event fails to index, Logstash writes it to the DLQ instead of dropping it silently. I've recovered hours of missing payment webhook logs from DLQ after a schema change broke the date parser — without DLQ, those events would have been permanently lost.

How do you optimize Elasticsearch for log workloads on limited hardware?

Elasticsearch defaults are designed for general-purpose search, not log analytics. Logs are append-only, time-series data with predictable access patterns. Tuning for this workload dramatically reduces resource requirements, which matters when you're self-hosting on Nepal-based VPS providers or budget-constrained EC2 instances.

SettingDefaultRecommended for LogsRationale
index.number_of_replicas10 (single node) or 1 (cluster)Logs are reproducible from source; replicas double storage cost
index.refresh_interval1s30sNear-real-time is sufficient; reduces segment creation overhead
index.translog.durabilityrequestasyncAccept minor data loss risk for 30-50% write throughput gain
index.codecdefaultbest_compressionLogs compress well; trades CPU for 30-40% storage savings
index.mapping.total_fields.limit10002000+Laravel context arrays can explode field count unexpectedly

Apply these settings via an index template so every new daily index inherits them automatically:

PUT _index_template/logs-laravel
{
  "index_patterns": ["logs-laravel-*"],
  "template": {
    "settings": {
      "number_of_replicas": 0,
      "refresh_interval": "30s",
      "translog.durability": "async",
      "codec": "best_compression",
      "mapping.total_fields.limit": 2000
    },
    "mappings": {
      "properties": {
        "@timestamp": { "type": "date" },
        "log.level": { "type": "keyword" },
        "log.message": { "type": "text" },
        "user.id": { "type": "keyword" },
        "error.type": { "type": "keyword" },
        "app": { "type": "keyword" }
      }
    }
  },
  "priority": 200
}

Explicit mappings prevent dynamic mapping explosions. If Laravel logs a new context field like order.metadata.items[].sku, Elasticsearch will dynamically map it. Over months, this creates thousands of fields that slow cluster state updates and consume heap. Define known fields explicitly and set dynamic: false on nested objects you don't need to query.

When should you choose OpenSearch over Elasticsearch in 2026?

The licensing divergence between Elasticsearch and OpenSearch continues to influence infrastructure decisions. Both are viable for centralized logging, but they serve different organizational constraints.

Start: Choose Log BackendNeed Elastic Cloud or proprietary features?YesNoElasticsearchSSPL LicenseElastic Cloud availableOpenSearchApache 2.0 LicenseAWS Managed AvailableBest for: Elastic ecosystem,advanced ML, commercial supportBest for: AWS shops, licensecompliance, community-driven
Decision framework for choosing between Elasticsearch and OpenSearch based on licensing and operational requirements

Choose Elasticsearch if you need Elastic Cloud managed hosting, proprietary features like ES|QL or advanced ML anomaly detection, or official commercial support contracts. The SSPL license restricts offering Elasticsearch as a managed service, but self-hosted internal use remains unrestricted.

Choose OpenSearch if you're already on AWS (Amazon OpenSearch Service is fully managed), require Apache 2.0 licensing for compliance reasons, or want to avoid vendor lock-in. OpenSearch forked from Elasticsearch 7.10 and maintains API compatibility for most logging use cases. Filebeat and Logstash work identically with both backends.

For Nepal-based deployments where budget sensitivity is high and AWS isn't mandatory, I typically recommend self-hosted OpenSearch on local VPS infrastructure. The Apache 2.0 license removes any future licensing uncertainty, and the community-maintained Docker images simplify deployment on modest hardware. Teams already invested in the Elastic ecosystem or requiring specific proprietary features should stick with Elasticsearch — the migration cost between the two is non-trivial once you've built custom dashboards and alerts.

Practical Next Steps for Production Deployment

Centralized logging with the ELK Stack transforms how you diagnose production issues, but only if implemented correctly from the start. Begin with structured JSON logging in your application before touching infrastructure — unstructured logs create technical debt that compounds over time. Deploy Filebeat with TLS, configure Logstash with DLQ enabled, and apply log-optimized index templates before ingesting your first production event. Start with a single-node Elasticsearch or OpenSearch instance; premature clustering adds operational complexity without proportional benefit until you exceed ~50GB of daily log volume.

If you're evaluating observability infrastructure for a Laravel application or need help designing a logging architecture that scales with your business, reach out to discuss your specific requirements. Proper logging setup pays for itself the first time you resolve a production incident in minutes instead of hours.

Frequently Asked Questions

The ELK Stack is Elasticsearch, Logstash, and Kibana—three open-source tools that collect, process, store, and visualize logs in one place. Elasticsearch indexes and searches log data at scale, Logstash parses and enriches logs before ingestion, and Kibana provides dashboards and alerts. For centralized logging, ELK eliminates scattered log files across servers, enables real-time debugging across microservices, and scales to terabytes of log volume without performance degradation. I've used ELK on production Laravel applications handling 50+ requests per second, where tailing individual log files was no longer practical.

A minimal ELK setup for a small Laravel app (10–50 requests/second) runs on a single t3.medium EC2 instance (2 vCPUs, 4 GB RAM) costing Rs 3,500/month (~USD 26). This covers Elasticsearch, Logstash, and Kibana on Ubuntu 24.04 with 100 GB EBS storage. Filebeat agents on application servers add negligible cost. For comparison, a managed Elastic Cloud starter plan begins at Rs 12,000/month (~USD 90), which removes server management overhead but lacks customization. In my experience, self-hosted ELK on a single instance is sufficient for most Laravel applications until log volume exceeds 100 GB/month.

For a production ELK Stack in 2026, start with Ubuntu 24.04 LTS, 4 GB RAM, 2 vCPUs, and 100 GB SSD storage. Elasticsearch 8.12+ requires Java 17+ and at least 2 GB heap (set via ES_JAVA_OPTS="-Xms2g -Xmx2g"). Logstash 8.12+ needs 1 GB heap. Kibana runs on Node.js 20 LTS and consumes 512 MB. For a Laravel application generating 1 GB logs/day, this configuration handles indexing, search, and visualization without performance issues. I've deployed this exact setup on AWS t3.medium instances for multiple production applications.

Install ELK on Ubuntu 24.04 in these steps: 1) Import Elastic GPG key: wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg. 2) Add Elastic repository: 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. 3) Update and install: sudo apt update && sudo apt install elasticsearch logstash kibana. 4) Configure Elasticsearch: edit /etc/elasticsearch/elasticsearch.yml, set cluster.name, node.name, network.host: 0.0.0.0, and discovery.type: single-node. 5) Start services: sudo systemctl enable --now elasticsearch logstash kibana. 6) Verify: curl -X GET "localhost:9200". This installs ELK 8.12+, the current stable release.

Use this Logstash configuration to parse Laravel logs (storage/logs/laravel.log) into structured Elasticsearch fields. Create /etc/logstash/conf.d/laravel.conf with input { file { path => "/var/www/laravel/storage/logs/laravel.log" start_position => "beginning" sincedb_path => "/dev/null" } }. filter { grok { match => { "message" => "\[%{TIMESTAMP_ISO8601:timestamp}\] %{LOGLEVEL:level}\: %{GREEDYDATA:message}" } }. date { match => ["timestamp", "yyyy-MM-dd HH:mm:ss"] target => "@timestamp" } }. output { elasticsearch { hosts => ["http://localhost:9200"] index => "laravel-logs-%{+YYYY.MM.dd}" } }. This grok pattern extracts timestamp, log level, and message into separate fields, enabling Kibana dashboards filtered by severity or time.

Secure ELK Stack 8.12+ with these steps: 1) Enable Elasticsearch security: edit /etc/elasticsearch/elasticsearch.yml, set xpack.security.enabled: true. 2) Generate passwords: sudo /usr/share/elasticsearch/bin/elasticsearch-setup-passwords auto. 3) Configure Kibana to use credentials: edit /etc/kibana/kibana.yml, set elasticsearch.username: "kibana_system" and elasticsearch.password: "". 4) Enable TLS: generate certificates with sudo /usr/share/elasticsearch/bin/elasticsearch-certutil ca && sudo /usr/share/elasticsearch/bin/elasticsearch-certutil cert --ca elastic-stack-ca.p12. 5) Configure TLS in elasticsearch.yml: xpack.security.http.ssl.enabled: true, xpack.security.http.ssl.keystore.path: /etc/elasticsearch/certs/http.p12. 6) Restart services: sudo systemctl restart elasticsearch kibana. This enables HTTPS on port 9200 and requires authentication for all requests.

Common ELK performance bottlenecks and fixes: 1) Elasticsearch heap pressure: increase heap size to 50% of available RAM (max 32 GB) via ES_JAVA_OPTS. 2) Slow Logstash parsing: optimize grok patterns and use multiple workers (pipeline.workers: 4). 3) High disk I/O: use SSD storage and increase index refresh interval (index.refresh_interval: 30s). 4) Kibana dashboard lag: reduce dashboard complexity and use saved searches. 5) Filebeat backpressure: increase queue size (queue.mem.events: 4096) and use load balancing. On a Laravel application generating 10 GB logs/day, I've resolved 90% of performance issues by adjusting these settings alone.

Set up Filebeat 8.12+ to ship logs from multiple servers: 1) Install Filebeat on each server: sudo apt install filebeat. 2) Configure /etc/filebeat/filebeat.yml: filebeat.inputs: - type: filestream paths: - /var/www/laravel/storage/logs/.log. 3) Configure Elasticsearch output: output.elasticsearch: hosts: ["https://elk-server:9200"] username: "filebeat_writer" password: "" ssl.certificate_authorities: ["/etc/filebeat/ca.crt"]. 4) Copy CA certificate from ELK server to /etc/filebeat/ca.crt. 5) Start Filebeat: sudo systemctl enable --now filebeat. 6) Verify: curl -X GET "https://elk-server:9200/_cat/indices?v". This configuration ships logs from 10+ servers to a central ELK instance without performance degradation.

Essential Kibana dashboards for Laravel applications: 1) Error Rate Dashboard: visualizes log level distribution (ERROR, WARNING, INFO) with time-series and top error messages. 2) Request Performance: tracks Laravel request duration (from log context) with percentiles and slow endpoint breakdown. 3) Queue Monitoring: displays failed jobs, processing time, and queue backlog. 4) Authentication: tracks login attempts, failed logins, and user activity. 5) Database Queries: visualizes slow queries and N+1 detection from Laravel debug logs. I've built these dashboards for production Laravel applications, reducing mean time to detection (MTTD) from hours to minutes for critical issues.

Rotate and archive ELK logs with these strategies: 1) Index Lifecycle Management (ILM): create a policy in Kibana (Stack Management → Index Lifecycle Policies) with hot-warm-cold phases. Set hot phase to 7 days, warm phase to 30 days, and cold phase to 90 days with searchable snapshots. 2) Curator: install elasticsearch-curator and create a cron job: 0 3 /usr/bin/curator --config /etc/curator/config.yml /etc/curator/action.yml. 3) S3 Archiving: configure repository-s3 plugin and snapshot indices older than 30 days to S3. For a Laravel application generating 1 GB logs/day, this reduces storage costs by 80% while maintaining 90-day searchability.

ELK Stack alternatives: 1) Grafana Loki: lightweight, cost-effective for logs-only use cases (no full-text search). 2) Graylog: easier setup, includes alerting and dashboards out-of-the-box. 3) Splunk: enterprise-grade with advanced analytics, but expensive (Rs 500,000+/year for 10 GB/day). 4) AWS OpenSearch: managed ELK-compatible service with built-in security. 5) ClickHouse + Vector: high-performance columnar storage for logs. For Laravel applications, I recommend ELK for teams needing full-text search and Kibana dashboards, Loki for cost-sensitive projects, and OpenSearch for managed cloud deployments.

Troubleshoot "failed to parse field" errors in Logstash: 1) Check Logstash logs: sudo journalctl -u logstash -f. 2) Verify grok pattern: test with the Grok Debugger in Kibana Dev Tools. 3) Add error handling: modify filter section: grok { match => { "message" => "..." } tag_on_failure => ["_grokparsefailure"] }. 4) Use mutate to remove problematic fields: mutate { remove_field => ["[field_name]"] }. 5) Check field mapping in Elasticsearch: curl -X GET "localhost:9200/laravel-logs/_mapping". For Laravel logs, I've resolved 90% of parsing errors by refining grok patterns to handle multi-line stack traces and JSON context fields.

Scale ELK Stack horizontally with these best practices: 1) Separate roles: dedicate nodes for master, data, and ingest. 2) Shard strategy: create indices with 1 primary shard per 50 GB data. 3) Load balancing: use multiple Logstash instances behind a load balancer. 4) Filebeat scaling: use load balancing and persistent queues. 5) Index patterns: use time-based indices (e.g., laravel-logs-2026.05.01). 6) Monitoring: enable Elasticsearch monitoring and set up alerts for cluster health. For a Laravel application scaling from 1 to 10 servers, I've implemented these practices to maintain sub-second search performance while handling 10x log volume growth.

Integrate Laravel logs with ELK using Monolog: 1) Install Monolog Elasticsearch handler: composer require elasticsearch/elasticsearch monolog/monolog. 2) Configure config/logging.php: 'channels' => [ 'elasticsearch' => [ 'driver' => 'monolog', 'handler' => \Monolog\Handler\ElasticsearchHandler::class, 'with' => [ 'client' => [ 'hosts' => ['http://elk-server:9200'] ], 'index' => 'laravel-logs', 'type' => '_doc', ], 'level' => 'debug', ], ]. 3) Set default log channel: 'default' => env('LOG_CHANNEL', 'elasticsearch'). 4) Configure Elasticsearch authentication in .env: ELASTICSEARCH_HOST=http://elk-server:9200 ELASTICSEARCH_USER=laravel_writer ELASTICSEARCH_PASS=password. This sends Laravel logs directly to Elasticsearch, bypassing Logstash for simpler deployments.

Exposing ELK Stack to the internet introduces these security risks: 1) Unauthenticated access: attackers can read sensitive logs (database credentials, user data). 2) Denial of service: public Elasticsearch clusters are frequently targeted for resource exhaustion. 3) Data exfiltration: logs containing PII or API keys can be scraped. 4) Remote code execution: vulnerable Logstash plugins can be exploited. Mitigations: 1) Place ELK behind a VPN or private subnet. 2) Enable Elasticsearch security (xpack.security.enabled: true). 3) Restrict Kibana to internal IPs. 4) Use network ACLs to allow only application servers. 5) Regularly update ELK components (8.12+). I've seen production ELK instances compromised within hours of public exposure, leading to data breaches and server hijacking.

Share this article

Quick Contact Options
Choose how you want to connect me: