How to Install Apache Kafka on Ubuntu 26.04 LTS (KRaft Mode, No ZooKeeper)
Table of Contents
- What is Apache Kafka and When Should You Use It
- KRaft Mode: Why ZooKeeper is No Longer Needed
- Prerequisites
- Step 1: Install Java 21
- Step 2: Create a Dedicated Kafka User
- Step 3: Download and Install Apache Kafka 4.2
- Step 4: Configure Kafka in KRaft Mode
- Step 5: Format the Storage Directory
- Step 6: Create a systemd Service
- Step 7: Configure UFW Firewall
- Step 8: Test with Producer and Consumer
- Step 9: Basic Topic Management
- Production Configuration Tips
- Kafka vs RabbitMQ: When to Use Which
- Common Issues and Quick Fixes
- Next Steps
Real-time data is everywhere — microservices firing events, IoT sensors streaming telemetry, user actions triggering notifications, logs flowing from dozens of servers simultaneously. Managing all of that reliably, at scale, without building a custom integration between every producer and every consumer, is exactly the problem Apache Kafka was built to solve.
This guide walks through installing Apache Kafka 4.2 on Ubuntu 26.04 LTS (Resolute Raccoon) using KRaft mode — the modern deployment approach that removes the ZooKeeper dependency entirely. If you’ve seen older Kafka tutorials that tell you to start ZooKeeper first, those are outdated: starting with Kafka 4.0, ZooKeeper support was completely removed.
What is Apache Kafka and When Should You Use It
Apache Kafka is a distributed event streaming platform — a durable, ordered log that producers write to and consumers read from. Unlike a traditional message queue where a message disappears after being consumed, Kafka retains events for a configurable period, allowing multiple independent consumers to read the same stream at their own pace.
Real-world use cases where Kafka fits well:
- Microservice decoupling — Service A publishes an event; Services B, C, and D each consume it independently, without A knowing or caring who’s listening.
- Log and metrics aggregation — Centralize logs from dozens of services into one stream, then fan out to Elasticsearch, S3, and a monitoring stack simultaneously.
- Real-time analytics — Process a stream of user events, transactions, or sensor readings as they happen rather than batching overnight.
- Event sourcing — Store every state change as an immutable event, with the ability to replay history to rebuild application state.
- Change Data Capture (CDC) — Stream database changes (inserts, updates, deletes) to downstream systems in real time.
Kafka is not the right tool for every messaging need. If you’re sending a task to exactly one worker and want it acknowledged once, a simpler queue (Redis Streams, RabbitMQ, or even PostgreSQL LISTEN/NOTIFY) is probably sufficient and far easier to operate. Kafka earns its complexity at scale.
KRaft Mode: Why ZooKeeper is No Longer Needed
Before Kafka 3.x, every Kafka deployment required a separate Apache ZooKeeper cluster to manage broker metadata, leader elections, and cluster state. This meant running and maintaining two distributed systems for every Kafka deployment — doubling the operational complexity.
KRaft (Kafka Raft Metadata mode) replaces ZooKeeper by moving cluster metadata management directly into Kafka itself, using the Raft consensus algorithm. The result:
- Simpler deployment — one system to install, configure, monitor, and upgrade instead of two.
- Faster startup and failover — Kafka no longer needs to synchronize with an external ZooKeeper cluster.
- Better scalability — ZooKeeper had practical limits on the number of partitions it could track; KRaft removes those limits.
- Kafka 4.0+: ZooKeeper support is completely removed. There is no option to use ZooKeeper with Kafka 4.x — KRaft is the only deployment mode.
Prerequisites
- Ubuntu 26.04 LTS (Resolute Raccoon) — fresh install or existing server
- Minimum 4GB RAM (8GB+ recommended for production workloads)
- At least 2 CPU cores
sudoaccess- Internet access to download packages
Step 1: Install Java 21
Kafka 4.x requires Java 17 or higher. Java 21 LTS is the recommended choice for Ubuntu 26.04 — it’s the current Long-Term Support release and ships cleanly from Ubuntu’s default repository:
sudo apt update
sudo apt install -y openjdk-21-jdk-headless
Verify the installation:
java -version
Expected output:
openjdk version "21.x.x" ...
OpenJDK Runtime Environment (build 21.x.x+...)
Set JAVA_HOME so Kafka can find it:
echo 'export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64' | \
sudo tee /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
# /usr/lib/jvm/java-21-openjdk-amd64
Step 2: Create a Dedicated Kafka User
Running Kafka as root is a security risk. Create a dedicated system user with no login access:
sudo useradd -r -m -U -d /opt/kafka -s /bin/false kafka
This follows the same principle of least privilege covered in our Docker Container Security Best Practices guide — services should run with the minimum access they need, never as root.
Step 3: Download and Install Apache Kafka 4.2
Set the version variables, then download and extract to /opt/kafka:
KAFKA_VERSION="4.2.0"
SCALA_VERSION="2.13"
wget https://downloads.apache.org/kafka/${KAFKA_VERSION}/kafka_${SCALA_VERSION}-${KAFKA_VERSION}.tgz \
-O /tmp/kafka.tgz
sudo tar -xzf /tmp/kafka.tgz -C /opt/kafka --strip-components=1
sudo chown -R kafka:kafka /opt/kafka
Verify the extraction:
ls /opt/kafka
# bin config libs LICENSE licenses NOTICE site-docs
Create a dedicated data directory for Kafka logs (separate from the application directory — good practice for disk management and backups):
sudo mkdir -p /var/lib/kafka/data
sudo chown -R kafka:kafka /var/lib/kafka
Step 4: Configure Kafka in KRaft Mode
KRaft mode uses a single unified configuration file (server.properties) where the broker also acts as the cluster controller. Edit the KRaft server configuration:
sudo nano /opt/kafka/config/kraft/server.properties
Key settings to review and update:
# The role this node plays in the cluster
# "broker,controller" = combined mode (suitable for single-node and small clusters)
process.roles=broker,controller
# Unique ID for this broker — change if running multiple brokers
node.id=1
# Controller quorum voters — format: node.id@host:port
# For single node: use the same node
controller.quorum.voters=1@localhost:9093
# Listeners — what addresses Kafka binds to
listeners=PLAINTEXT://localhost:9092,CONTROLLER://localhost:9093
# The address clients use to reach the broker
# Change 'localhost' to your server's actual IP or hostname if remote clients need access
advertised.listeners=PLAINTEXT://localhost:9092
# Listener used for inter-broker and controller communication
inter.broker.listener.name=PLAINTEXT
controller.listener.names=CONTROLLER
# Log directory — where Kafka stores event data
log.dirs=/var/lib/kafka/data
# Number of partitions for auto-created topics
num.partitions=3
# Log retention (how long to keep events)
log.retention.hours=168 # 7 days
log.retention.bytes=1073741824 # 1GB per partition
# Replication factor for internal topics (keep at 1 for single-node)
offsets.topic.replication.factor=1
transaction.state.log.replication.factor=1
transaction.state.log.min.isr=1
Remote access note: If clients will connect from other machines, replace
localhostinadvertised.listenerswith your server’s actual IP address or DNS hostname. Kafka clients use the advertised address to establish connections — usinglocalhosthere will cause remote clients to fail even if they can reach the server on port 9092.
Step 5: Format the Storage Directory
Before first startup, Kafka’s storage directory must be initialized with a unique cluster ID. This is a one-time operation:
# Generate a cluster ID
CLUSTER_ID=$(/opt/kafka/bin/kafka-storage.sh random-uuid)
echo "Cluster ID: $CLUSTER_ID"
# Format the storage directory with that cluster ID
sudo -u kafka /opt/kafka/bin/kafka-storage.sh format \
-t $CLUSTER_ID \
-c /opt/kafka/config/kraft/server.properties
Expected output:
Formatting /var/lib/kafka/data with metadata.version X.X-IV...
This step replaces the old ZooKeeper initialization — there’s no zookeeper-server-start.sh to run first.
Step 6: Create a systemd Service
Create a systemd unit file so Kafka starts automatically on boot and can be managed with systemctl:
sudo nano /etc/systemd/system/kafka.service
[Unit]
Description=Apache Kafka Server (KRaft Mode)
Documentation=https://kafka.apache.org/documentation/
After=network.target
[Service]
Type=simple
User=kafka
Group=kafka
Environment="JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64"
ExecStart=/opt/kafka/bin/kafka-server-start.sh \
/opt/kafka/config/kraft/server.properties
ExecStop=/opt/kafka/bin/kafka-server-stop.sh
Restart=on-failure
RestartSec=10
LimitNOFILE=65536
StandardOutput=journal
StandardError=journal
SyslogIdentifier=kafka
[Install]
WantedBy=multi-user.target
Enable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable kafka
sudo systemctl start kafka
Check the status:
sudo systemctl status kafka
Expected output:
● kafka.service - Apache Kafka Server (KRaft Mode)
Loaded: loaded (/etc/systemd/system/kafka.service; enabled)
Active: active (running) since ...
View live logs:
sudo journalctl -u kafka -f
Step 7: Configure UFW Firewall
If UFW is enabled, open the ports Kafka needs:
# Port 9092 — Kafka broker (clients connect here)
sudo ufw allow 9092/tcp comment "Kafka broker"
# Port 9093 — Kafka controller (internal KRaft communication)
# Only needed if running multi-node — restrict to internal network for single-node
sudo ufw allow from 10.0.0.0/8 to any port 9093 comment "Kafka KRaft controller"
sudo ufw reload
sudo ufw status
Security note: Port 9092 should only be open to trusted clients, not the entire internet. If your Kafka broker is internet-facing, restrict port 9092 to specific IP ranges or put it behind a VPN.
Step 8: Test with Producer and Consumer
Open two separate terminal sessions for this test — one for the producer, one for the consumer.
First: Create a test topic
sudo -u kafka /opt/kafka/bin/kafka-topics.sh \
--create \
--topic test-events \
--partitions 3 \
--replication-factor 1 \
--bootstrap-server localhost:9092
Expected output:
Created topic test-events.
Terminal 1: Start a producer and send messages
sudo -u kafka /opt/kafka/bin/kafka-console-producer.sh \
--topic test-events \
--bootstrap-server localhost:9092
Type a few messages and press Enter after each:
> Hello from Kafka on Ubuntu 26.04
> This is a test message
> Apache Kafka 4.2 KRaft mode works!
Press Ctrl+C to exit.
Terminal 2: Start a consumer and read messages
sudo -u kafka /opt/kafka/bin/kafka-console-consumer.sh \
--topic test-events \
--from-beginning \
--bootstrap-server localhost:9092
You should see the messages you typed appear immediately. Press Ctrl+C to exit.
--from-beginning tells the consumer to read from the earliest available offset — not just new messages. This illustrates one of Kafka’s key properties: consumers can replay historical events, not just receive new ones.
Step 9: Basic Topic Management
# List all topics
sudo -u kafka /opt/kafka/bin/kafka-topics.sh \
--list \
--bootstrap-server localhost:9092
# Describe a topic (partitions, replicas, leader)
sudo -u kafka /opt/kafka/bin/kafka-topics.sh \
--describe \
--topic test-events \
--bootstrap-server localhost:9092
# Increase partition count (partitions can only be increased, never decreased)
sudo -u kafka /opt/kafka/bin/kafka-topics.sh \
--alter \
--topic test-events \
--partitions 6 \
--bootstrap-server localhost:9092
# Delete a topic
sudo -u kafka /opt/kafka/bin/kafka-topics.sh \
--delete \
--topic test-events \
--bootstrap-server localhost:9092
# List consumer groups
sudo -u kafka /opt/kafka/bin/kafka-consumer-groups.sh \
--list \
--bootstrap-server localhost:9092
# Check consumer group lag (how far behind a consumer is)
sudo -u kafka /opt/kafka/bin/kafka-consumer-groups.sh \
--describe \
--group my-consumer-group \
--bootstrap-server localhost:9092
Consumer group lag (last command) is one of the most important operational metrics in a Kafka deployment — it tells you how many messages a consumer is behind. Integrating this metric into your Prometheus + Grafana monitoring stack via kafka-exporter or JMX exporter gives you real-time visibility into consumer health.
Production Configuration Tips
A single-node Kafka installation on Ubuntu is sufficient for development and low-traffic production workloads. For anything that needs to scale or survive a broker failure, here are the key settings to revisit:
Memory tuning:
Kafka’s JVM heap is set to 1GB by default. For production, adjust in /opt/kafka/bin/kafka-server-start.sh:
export KAFKA_HEAP_OPTS="-Xmx4G -Xms4G"
Set both -Xmx and -Xms to the same value to prevent JVM heap resizing pauses.
Log retention by size, not just time:
# Retain logs for 7 days OR until they exceed 10GB per partition, whichever comes first
log.retention.hours=168
log.retention.bytes=10737418240
Auto topic creation in production:
# Disable auto-creation — require topics to be created explicitly
auto.create.topics.enable=false
Auto-created topics use default partition/replication settings, which are rarely correct for specific use cases. Disable this and create topics explicitly with the right parameters.
Replication factor for multi-broker clusters:
# With 3 brokers, use replication factor 3 and min ISR 2
# (data survives loss of any 1 broker, writes require 2 brokers to acknowledge)
default.replication.factor=3
min.insync.replicas=2
Regular backups:
The /var/lib/kafka/data directory contains all event data. Back it up the same way you’d back up any other data volume — a scheduled snapshot with retention policy, tested restore process, stored separately from the host. The scripted approach from our Redis with Docker Compose guide applies directly to the Kafka data directory.
Kafka vs RabbitMQ: When to Use Which
A common question when introducing a message broker for the first time:
| Apache Kafka | RabbitMQ | |
|---|---|---|
| Model | Distributed log (pull-based) | Message queue (push-based) |
| Message retention | Configurable period (days/weeks) | Until acknowledged (usually) |
| Throughput | Millions of messages/second | Tens to hundreds of thousands/second |
| Consumer pattern | Each consumer group reads the full log independently | One consumer per message (competing consumers) |
| Replay | Yes — consumers can re-read past events | No — once consumed, message is gone |
| Ordering guarantee | Per partition | Per queue |
| Operational complexity | Higher | Lower |
| Best for | Event streaming, log aggregation, CDC, high throughput | Task queues, RPC, point-to-point messaging, simpler use cases |
Choose Kafka when: You need multiple consumers to independently process the same events, you need to replay history, or you need to handle very high throughput (millions of events/second).
Choose RabbitMQ when: You’re building a task queue where each task should be processed exactly once by exactly one worker, the message volume is moderate, and you want simpler operations.
Common Issues and Quick Fixes
| Symptom | Likely Cause | Fix |
|---|---|---|
kafka.service fails to start | Storage not formatted, or wrong JAVA_HOME | Run kafka-storage.sh format first; verify java -version works as kafka user |
Connection refused on port 9092 | Kafka not running, or listener bound to wrong address | Check systemctl status kafka; verify listeners in server.properties |
| Remote clients can’t connect | advertised.listeners set to localhost | Change advertised.listeners to the server’s actual IP or hostname |
Leader not available on topic create | Kafka still initializing after start | Wait 10-15 seconds after Kafka starts before creating topics |
| Consumer reads no messages | Consumer started after producer, no --from-beginning flag | Add --from-beginning flag to read historical messages |
OutOfMemoryError in logs | JVM heap too small for the workload | Increase -Xmx and -Xms in kafka-server-start.sh |
| High consumer group lag | Consumer too slow or too few consumer instances | Scale consumer instances; check processing logic for bottlenecks |
Conclusion
With Kafka running on Ubuntu 26.04, the natural next steps are:
- Add monitoring — deploy
kafka-exporteralongside your Prometheus + Grafana stack to track consumer group lag, message throughput, and broker health in real time. - Containerize it — for teams already running Docker Compose stacks, Kafka has an official Docker image and can be added to an existing Compose setup using the same KRaft configuration.
- Explore Kafka Streams or Apache Flink — for in-stream processing (filtering, aggregating, joining streams) without writing a separate consumer application.
- Scale to multi-broker — repeat the installation on additional nodes, adjust
broker.idandcontroller.quorum.voters, and distribute your topics’ partitions across the cluster for fault tolerance and horizontal throughput scaling.







