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
sudoprivileges. - Nginx installed, so
/var/log/nginx/access.logexists (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:
- Inputs read data (files, the systemd journal, syslog, TCP, metrics) and attach a tag to each record.
- Parsers turn unstructured lines into key/value records.
- Filters modify, enrich or drop records.
- 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
tailorsystemdinput 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 5sends buffered records to outputs every five seconds.HTTP_Serverexposes a monitoring API on127.0.0.1:2020;Health_Check Onenables the/api/v1/healthendpoint.Parser nginxsplits each access log line intoremote,user,method,path,code,size,refererandagent, and uses the log's own time as the record timestamp.Mem_Buf_Limitcaps how much memory the tail input may use. Withstorage.type filesystem, records beyond that limit go tostorage.pathinstead of pausing the input.Read_From_Tail Onmakes the journal input start with new messages rather than the whole journal.- The
stdoutoutput 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.
