Fluent Bit is a lightweight log processor and forwarder written in C, part of the Fluentd project under the CNCF. It typically runs with a few megabytes of memory, which makes it a good agent for every server in a fleet. In this tutorial you will install Fluent Bit on Ubuntu 24.04 from the official repository, collect the Nginx access log and SSH logs from the systemd journal, parse and filter the records, buffer them on disk and forward them to Grafana Loki.

Prerequisites

  • A server running Ubuntu 24.04 LTS, for example a CubePath VPS.
  • A non-root user with sudo privileges.
  • Nginx installed, so /var/log/nginx/access.log exists (sudo apt install nginx).
  • Optional: a Grafana Loki instance reachable from the server.

How Fluent Bit processes logs

Fluent Bit moves records through a fixed pipeline:

  1. Inputs read data (files, the systemd journal, syslog, TCP, metrics) and attach a tag to each record.
  2. Parsers turn unstructured lines into key/value records.
  3. Filters modify, enrich or drop records.
  4. Outputs deliver them (stdout, Loki, Elasticsearch, S3, another Fluent Bit or Fluentd).

Filters and outputs select records with a Match pattern against the tag, for example nginx.*. This tutorial uses the classic configuration format (.conf), which is what the package ships by default.

Step 1 - Installing Fluent Bit from the official repository

Ubuntu's archive does not carry current Fluent Bit releases, so use the project's APT repository. Download the signing key into a dedicated keyring:

sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://packages.fluentbit.io/fluentbit.key | sudo gpg --dearmor -o /etc/apt/keyrings/fluentbit.gpg

Add the repository for Ubuntu 24.04 (codename noble):

echo "deb [signed-by=/etc/apt/keyrings/fluentbit.gpg] https://packages.fluentbit.io/ubuntu/noble noble main" | sudo tee /etc/apt/sources.list.d/fluent-bit.list

Install the package:

sudo apt update
sudo apt install -y fluent-bit

The package installs the binary in /opt/fluent-bit/bin/, which is not in your PATH. Check the version with the full path:

/opt/fluent-bit/bin/fluent-bit --version
Fluent Bit v4.0.3

Configuration files live in /etc/fluent-bit/: fluent-bit.conf is the main file and parsers.conf contains ready-made parsers, including nginx, apache2, json, docker and syslog-rfc5424.

Step 2 - Preparing directories for state and buffers

Fluent Bit keeps two kinds of state on disk:

  • A small SQLite database per tail or systemd input that records the last read position, so a restart does not re-send or skip logs.
  • Filesystem buffer chunks, so records waiting for an unavailable output survive a restart and do not pile up in memory.

Create a directory for both:

sudo mkdir -p /var/lib/fluent-bit/storage

The packaged systemd unit runs Fluent Bit as root, so it can read /var/log/nginx and the journal without extra group changes.

Step 3 - Writing the configuration

Back up the default configuration and create a new one:

sudo mv /etc/fluent-bit/fluent-bit.conf /etc/fluent-bit/fluent-bit.conf.orig
sudo nano /etc/fluent-bit/fluent-bit.conf

Paste the following. In the classic format, comments must be on their own line; a # after a value becomes part of the value.

[SERVICE]
    Flush                     5
    Log_Level                 info
    Daemon                    off
    Parsers_File              parsers.conf
    HTTP_Server               On
    HTTP_Listen               127.0.0.1
    HTTP_Port                 2020
    Health_Check              On
    storage.path              /var/lib/fluent-bit/storage
    storage.sync              normal
    storage.backlog.mem_limit 5M

# Nginx access log, parsed with the stock nginx parser
[INPUT]
    Name              tail
    Tag               nginx.access
    Path              /var/log/nginx/access.log
    Parser            nginx
    DB                /var/lib/fluent-bit/nginx-access.db
    Mem_Buf_Limit     10MB
    Skip_Long_Lines   On
    storage.type      filesystem

# SSH daemon messages from the systemd journal
[INPUT]
    Name              systemd
    Tag               host.ssh
    Systemd_Filter    _SYSTEMD_UNIT=ssh.service
    DB                /var/lib/fluent-bit/journal.db
    Read_From_Tail    On
    Strip_Underscores On
    storage.type      filesystem

# Drop load balancer health checks
[FILTER]
    Name    grep
    Match   nginx.*
    Exclude path ^/health$

# Add a static field to every record
[FILTER]
    Name    modify
    Match   *
    Add     env production

[OUTPUT]
    Name    stdout
    Match   *
    Format  json_lines

Key settings:

  • Flush 5 sends buffered records to outputs every five seconds.
  • HTTP_Server exposes a monitoring API on 127.0.0.1:2020; Health_Check On enables the /api/v1/health endpoint.
  • Parser nginx splits each access log line into remote, user, method, path, code, size, referer and agent, and uses the log's own time as the record timestamp.
  • Mem_Buf_Limit caps how much memory the tail input may use. With storage.type filesystem, records beyond that limit go to storage.path instead of pausing the input.
  • Read_From_Tail On makes the journal input start with new messages rather than the whole journal.
  • The stdout output prints records to Fluent Bit's own log, which lands in the journal. It is for testing and is replaced by Loki in Step 6.

Step 4 - Testing and starting the service

Check the configuration without starting the pipeline:

sudo /opt/fluent-bit/bin/fluent-bit -c /etc/fluent-bit/fluent-bit.conf --dry-run
configuration test is successful

Enable and start the service:

sudo systemctl enable --now fluent-bit
sudo systemctl status fluent-bit --no-pager

The status should show active (running). If it was already running from the package install, restart it with sudo systemctl restart fluent-bit.

Step 5 - Verifying the pipeline

Generate a few requests, including one that the grep filter should drop:

curl -s -o /dev/null http://localhost/
curl -s -o /dev/null http://localhost/does-not-exist
curl -s -o /dev/null http://localhost/health

Read the records from the journal:

sudo journalctl -u fluent-bit -n 20 --no-pager

You should see JSON lines like this one, with parsed fields and the added env key, and no record for /health:

{"date":1773150222.0,"remote":"127.0.0.1","host":"-","user":"-","method":"GET","path":"/does-not-exist","code":"404","size":"162","referer":"-","agent":"curl/8.5.0","env":"production"}

Open a new SSH session to the server and check the journal again: a record tagged host.ssh with a MESSAGE field such as Accepted publickey for ... confirms the systemd input works.

Query the monitoring API:

curl -s http://127.0.0.1:2020/api/v1/health
ok
curl -s http://127.0.0.1:2020/api/v1/metrics | python3 -m json.tool | head -n 20

The metrics show records and bytes per input and output, plus retries and errors per output. A Prometheus-formatted version is available at /api/v1/metrics/prometheus.

Step 6 - Forwarding logs to Grafana Loki

Once the records look right, replace the stdout output with Loki. Open the configuration:

sudo nano /etc/fluent-bit/fluent-bit.conf

Replace the [OUTPUT] section with the following, setting Host to your Loki server:

[OUTPUT]
    Name        loki
    Match       *
    Host        loki.example.internal
    Port        3100
    Labels      job=fluent-bit, env=production
    Line_Format json
    Retry_Limit 10

Retry_Limit controls how many times a failed chunk is retried; combined with filesystem buffering, short Loki outages do not lose data. Keep labels few and static; per-request values such as path or remote should stay inside the JSON line, where LogQL can filter them.

If your Loki endpoint uses TLS and basic authentication, add these keys to the same section:

    tls         On
    tls.verify  On
    HTTP_User   your_loki_user
    HTTP_Passwd your_loki_password

Test and restart:

sudo /opt/fluent-bit/bin/fluent-bit -c /etc/fluent-bit/fluent-bit.conf --dry-run
sudo systemctl restart fluent-bit

In Grafana, the query {job="fluent-bit"} | json | code >= 400 lists client and server errors from Nginx. On the server, journalctl -u fluent-bit should no longer show retry or connection errors for the loki output.

Troubleshooting

Records are missing or appear twice after a restart. Check that each tail and systemd input has its own DB file. Without it, Fluent Bit does not remember positions.

Nginx records contain only a log field. The line did not match the nginx parser, usually because of a custom log_format. Write a matching regex parser in /etc/fluent-bit/parsers.conf, or switch Nginx to a JSON log format and use the json parser.

Memory keeps growing while an output is down. Make sure storage.path is set in [SERVICE] and storage.type filesystem is set on the inputs, and keep Mem_Buf_Limit small.

The service fails right after a change. Run the --dry-run command above, then sudo journalctl -u fluent-bit -n 50 for the exact error. A comment placed at the end of a value line is a common cause.

Conclusion

Fluent Bit now tails Nginx and journal logs on your Ubuntu 24.04 server, parses and filters them, buffers them on disk and ships them to Loki. From here you can add more tail inputs for application logs, use the lua filter for custom logic that the built-in filters cannot express, or deploy the same pipeline to Kubernetes with the official Helm chart at https://fluent.github.io/helm-charts.