Streaming & Messaging AWS

AKHQ for Apache Kafka 0.28.0 on AWS User Guide

| Product: AKHQ for Apache Kafka 0.28.0 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the AKHQ for Apache Kafka 0.28.0 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg streaming and messaging listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

AKHQ is an open source web UI for Apache Kafka. From one browser tab you browse topics and their configuration, read and search messages (with a live tail), produce test messages, watch consumer groups and their lag, reset offsets, inspect brokers and their configuration, and manage schema registry subjects, Kafka Connect connectors, ksqlDB and ACLs. It talks to any Kafka cluster it can reach: Amazon MSK, Confluent Platform, Redpanda or your own brokers.

What the cloudimg image adds:

  • No default login, no shared secret. The admin password and the secret that signs AKHQ's login sessions are generated on your instance at first boot and written to /etc/akhq/secrets.yml (root and the akhq group only) and /root/akhq-credentials.txt (root only). The image ships neither file, and AKHQ's built-in default session secret is never used.
  • Login required for everything. AKHQ security is switched on: an anonymous visitor gets no role at all, HTTP Basic authentication is off, and the only way in is the login form, which nginx rate limits per client address.
  • HTTPS from the first boot. nginx serves AKHQ on port 443 with a certificate generated on your instance, port 80 redirects to HTTPS and requests for host names the instance does not serve are dropped. A request carrying another site's Origin is refused, and so is every WebSocket upgrade (AKHQ uses Server-Sent Events for its live tail).
  • AKHQ and Kafka on localhost only. AKHQ listens on 127.0.0.1:8080 (and its health endpoint on 127.0.0.1:28081), and its service refuses to stay up if it ever finds itself on another address. The bundled demo broker listens on 127.0.0.1:9092.
  • Fail closed. Kafka, AKHQ and nginx start only after the boot service has written your secrets. Before AKHQ starts, a configuration check refuses to run it with a missing or default secret, with HTTP Basic or anonymous access switched on, or on a public address.
  • Hardened defaults. AKHQ, Kafka, nginx and the boot service are blocked from the instance metadata service (AKHQ connects to the addresses you configure, so a mistake or a malicious setting can never reach the instance's credentials), the AMI requires IMDSv2, nginx hands AKHQ the real client address and the configured host name (never values a client sends), and both Java services run under systemd sandboxing as unprivileged users.
  • Ready to explore. A single-node Apache Kafka 4.4.0 broker (KRaft) is preloaded with three demo topics, sample messages and a consumer group with lag, so every AKHQ screen shows real data on first login. Point AKHQ at your own cluster when you are ready (Step 7).
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: AKHQ 0.28.0 (the upstream release jar, verified against its published sha256 digest) on Eclipse Temurin 25.0.5 JRE, Apache Kafka 4.4.0 on OpenJDK 21 (from Ubuntu 24.04), nginx 1.24 (from Ubuntu 24.04), cloud-init and the AWS Systems Manager agent.

Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large

The AKHQ Topics page listing the demo-orders, demo-payments and demo-users topics with the demo-consumer group and its lag

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
  • A security group that allows TCP 22 (SSH) from your admin network and TCP 443 (the AKHQ web UI) from the networks your users browse from, plus TCP 80 if you want plain HTTP requests redirected to HTTPS. Never open 8080, 28081, 9092 or 9093: they listen on the loopback address only.
  • To manage your own Kafka cluster: network access from the instance to your brokers (for Amazon MSK, allow the instance's security group on the cluster's security group for the SASL/SCRAM port 9096 or the TLS port 9094).

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick AKHQ for Apache Kafka 0.28.0 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair and a security group as above.

With the AWS CLI (replace the values in angle brackets):

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=akhq}]'

First boot takes about a minute after the instance is running: the boot service generates the secrets and the certificate and formats the demo broker's storage, then Kafka loads the demo data and AKHQ starts.

Step 2: Connect to your instance

Version SSH user
AKHQ for Apache Kafka 0.28.0 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

systemctl is-active akhq-firstboot kafka akhq nginx
sudo test -f /var/lib/cloudimg/akhq-firstboot.done && echo FIRSTBOOT_DONE

All four units are active and the first boot marker is present:

active
active
active
active
FIRSTBOOT_DONE

Only SSH and nginx listen on network ports; AKHQ (8080, 28081) and Kafka (9092, 9093) listen on the loopback address:

sudo ss -lntp | awk 'NR>1 {print $4, $6}' | grep -v '127.0.0.5' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | sort
0.0.0.0:22 users:(("sshd"),("systemd"))
0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"))
0.0.0.0:80 users:(("nginx"),("nginx"),("nginx"))
[::]:22 users:(("sshd"),("systemd"))
[::]:443 users:(("nginx"),("nginx"),("nginx"))
[::]:80 users:(("nginx"),("nginx"),("nginx"))
[::ffff:127.0.0.1]:28081 users:(("java"))
[::ffff:127.0.0.1]:8080 users:(("java"))
[::ffff:127.0.0.1]:9092 users:(("java"))
[::ffff:127.0.0.1]:9093 users:(("java"))

Step 4: Get your URL and admin password

sudo cat /root/akhq-credentials.txt

The file is readable by root only:

# AKHQ for Apache Kafka 0.28.0 on Ubuntu 24.04 - generated on first boot by akhq-firstboot.service.
# The admin password below is unique to this instance; none was baked into the image. Keep this file secret.
# Open AKHQ_URL in a browser and log in as AKHQ_ADMIN_USER. The certificate is self signed (minted on this
# instance); install your own in /etc/nginx/ssl. Change the served name: sudo akhq-set-hostname <dns-name>
AKHQ_URL=https://<public-ip>/
AKHQ_ADMIN_USER=admin
AKHQ_ADMIN_PASSWORD=<AKHQ_ADMIN_PASSWORD>
# Re-verify the appliance: sudo akhq-selfcheck    Connect your own Kafka cluster: /etc/akhq/application.yml
# User guide: https://www.cloudimg.co.uk/guides/akhq-for-apache-kafka-0-28-0-on-ubuntu-24-04-aws/

The URL uses the instance's public IP address from the EC2 instance metadata (the private address when the instance has no public one). The certificate also names the public DNS name and the private IP address. If you stop and start the instance without an Elastic IP, the boot service picks up the new address and issues a new certificate for it. To serve AKHQ under your own DNS name, see Step 9.

Step 5: The AKHQ web UI

Open https://<public-ip>/ in your browser, accept the self signed certificate and log in as admin with AKHQ_ADMIN_PASSWORD.

The AKHQ login page

The cluster cloudimg is the preloaded demo broker. Topics (shown at the top of this guide) lists every topic with its message count, size, partitions and the consumer groups reading it. Open a topic to read its messages: AKHQ shows key, timestamp, partition, offset and the decoded value, and you can sort, filter by partition or time, search, download results, produce a message or start a Live Tail:

The demo-orders topic in AKHQ with JSON order messages, their keys, partitions and offsets

Consumer Groups shows each group's state, coordinator, members and lag per topic. The demo group demo-consumer has read the first 20 orders and lags behind the 8 produced after it:

The Consumer Groups page showing demo-consumer with a lag of 8 on demo-orders

Nodes lists the brokers with their partition share; open a node for its full configuration:

The Nodes page showing broker 1 at 127.0.0.1:9092 as controller with 5 partitions

Step 6: Use the AKHQ API

Everything in the UI is also available as a JSON API under /api. Without a session it answers 401:

curl -sk -m 10 -o /dev/null -w '%{http_code}\n' -H 'Accept: application/json' https://127.0.0.1/api/cloudimg/topic
401

Log in through the same form the browser uses, keep the session cookie, and call the API. On the instance the commands use https://127.0.0.1/ (the instance's own names are all served); from elsewhere use your AKHQ_URL. The password goes to curl on standard input, never on the command line, and the cookie jar is removed at the end:

AKHQ_URL=https://127.0.0.1/
JAR=$(mktemp)
sudo sed -n 's/^AKHQ_ADMIN_PASSWORD=//p' /root/akhq-credentials.txt | tr -d '\n' \
  | curl -sk -m 20 -c "$JAR" -o /dev/null -w '%{http_code} %{redirect_url}\n' --data-urlencode username=admin --data-urlencode password@- "${AKHQ_URL}login"
curl -sk -m 20 -b "$JAR" -H 'Accept: application/json' "${AKHQ_URL}api/cloudimg/topic" | jq -r '.results[] | "\(.name) partitions=\(.partitions | length)"'
curl -sk -m 20 -b "$JAR" -H 'Accept: application/json' "${AKHQ_URL}api/cloudimg/group" | jq -c '.results[] | {id, state}'
curl -sk -m 20 -b "$JAR" -H 'Accept: application/json' "${AKHQ_URL}api/cloudimg/topic/demo-orders/data" | jq -r '.results[:3][] | "\(.key) \(.value)"'
rm -f "$JAR"

A successful login answers 303 back to the UI with the session cookie set (a wrong password answers 303 to the login page with no cookie):

303 https://127.0.0.1/ui
demo-orders partitions=3
demo-payments partitions=1
demo-users partitions=1
{"id":"demo-consumer","state":"Empty"}
order-001 {"orderId":1001,"customer":"cust-02","item":"SKU-0037","qty":2,"amount":22.99,"status":"PLACED"}
order-006 {"orderId":1006,"customer":"cust-07","item":"SKU-0222","qty":2,"amount":87.99,"status":"PLACED"}
order-002 {"orderId":1002,"customer":"cust-03","item":"SKU-0074","qty":3,"amount":35.99,"status":"PLACED"}

Sessions last one hour; log in again after that.

Step 7: Connect your own Kafka cluster

AKHQ reads its clusters from /etc/akhq/application.yml (owned by root, readable by the akhq group). Add a connection under akhq.connections next to the demo cloudimg entry. For Amazon MSK with SASL/SCRAM (the cluster's bootstrap string from the MSK console, port 9096; the user and password are the ones stored in AWS Secrets Manager for the cluster):

akhq:
  connections:
    cloudimg:
      properties:
        bootstrap.servers: "127.0.0.1:9092"
    my-msk:
      properties:
        bootstrap.servers: "b-1.mycluster.abc123.c2.kafka.us-east-1.amazonaws.com:9096,b-2.mycluster.abc123.c2.kafka.us-east-1.amazonaws.com:9096"
        security.protocol: SASL_SSL
        sasl.mechanism: SCRAM-SHA-512
        sasl.jaas.config: org.apache.kafka.common.security.scram.ScramLoginModule required username="<msk-user>" password="<msk-password>";

For mutual TLS (MSK TLS client authentication on port 9094, or your own brokers), copy the keystore and truststore into /etc/akhq/ with owner root:akhq and mode 0640, then:

    my-mtls-cluster:
      properties:
        bootstrap.servers: "broker-1.example.internal:9094"
        security.protocol: SSL
        ssl.truststore.location: /etc/akhq/kafka.truststore.jks
        ssl.truststore.password: "<truststore-password>"
        ssl.keystore.location: /etc/akhq/kafka.keystore.jks
        ssl.keystore.password: "<keystore-password>"
        ssl.key.password: "<key-password>"

A schema registry or a Kafka Connect cluster joins the same connection (schema-registry: {url: ...}, connect: [{name: ..., url: ...}]; see the AKHQ configuration documentation). Then restart AKHQ and watch it come up:

sudo systemctl restart akhq && sudo journalctl -u akhq -n 20 --no-pager

Notes:

  • Amazon MSK IAM authentication is not available from this image. IAM authentication needs the instance-role credentials from the instance metadata service, which this image blocks for AKHQ on purpose (it is what keeps a malicious or mistaken connection setting from reaching the instance's credentials). Use SASL/SCRAM or mutual TLS, both of which MSK supports alongside IAM on the same cluster.
  • Keep micronaut.server.host at 127.0.0.1 and do not enable micronaut.server.ssl: AKHQ's configuration check refuses to start otherwise, and nginx already provides HTTPS.
  • /etc/akhq/application.yml holds your cluster passwords; it is not world readable, and it is the file to back up (Step 13).

Step 8: The demo broker

The demo broker is a single-node Apache Kafka 4.4.0 (KRaft) on 127.0.0.1:9092, with its data in /var/lib/kafka/data and its settings in /etc/kafka/server.properties. Every instance formats its own fresh cluster at first boot. Kafka's command line tools are in /opt/kafka/bin:

LOG_DIR=/tmp/kafka-cli KAFKA_JMX_OPTS=-Dcli=1 /opt/kafka/bin/kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --list
LOG_DIR=/tmp/kafka-cli KAFKA_JMX_OPTS=-Dcli=1 /opt/kafka/bin/kafka-consumer-groups.sh --bootstrap-server 127.0.0.1:9092 --describe --group demo-consumer
__consumer_offsets
demo-orders
demo-payments
demo-users

GROUP           TOPIC           PARTITION  CURRENT-OFFSET  LOG-END-OFFSET  LAG             CONSUMER-ID     HOST            CLIENT-ID
demo-consumer   demo-orders     0          6               9               3               -               -               -
demo-consumer   demo-orders     1          6               9               3               -               -               -
demo-consumer   demo-orders     2          8               10              2               -               -               -

The broker accepts connections from this instance only and has no authentication, so it is a sandbox, not a production cluster. Delete the demo-* topics and the demo-consumer group whenever you like. Once AKHQ points at your own cluster you can remove the cloudimg connection from /etc/akhq/application.yml and switch the broker off (AKHQ does not depend on it):

sudo systemctl disable --now kafka && sudo systemctl restart akhq

Step 9: Your own DNS name and certificate

Point a DNS record at the instance, then tell AKHQ to serve that name. This updates nginx, issues a new self signed certificate that names it, accepts https://<name> as an allowed origin and updates the URL in the credentials file:

sudo akhq-set-hostname akhq.<your-domain>

The name is saved in /etc/cloudimg/akhq-hostname and survives reboots. To use a certificate from your own CA or from Let's Encrypt, replace /etc/nginx/ssl/akhq.crt and /etc/nginx/ssl/akhq.key (keep the key mode 0600) and run sudo systemctl reload nginx.

Step 10: Users, passwords and the session secret

/etc/akhq/secrets.yml holds the session-signing secret and the users. Passwords are stored as SHA-256 hashes (passwordHash: SHA256; AKHQ also accepts BCRYPT). Groups map users to AKHQ roles: admin has full access; for a read-only user add a reader group to akhq.security.groups in application.yml (see the AKHQ security documentation) and give the user that group.

To change the admin password, run the block below. It reads the new password without echoing it, writes its hash into secrets.yml and the password into the credentials file (through standard input, never the command line), then restarts AKHQ:

read -rsp 'New AKHQ admin password: ' P; echo
printf '%s' "$P" | sudo python3 -I -c '
import hashlib, re, sys
p = sys.stdin.read()
h = hashlib.sha256(p.encode()).hexdigest()
s = open("/etc/akhq/secrets.yml").read()
s = re.sub(r"(password: \")[0-9a-f]{64}(\")", lambda m: m.group(1) + h + m.group(2), s, count=1)
open("/etc/akhq/secrets.yml", "w").write(s)
c = open("/root/akhq-credentials.txt").read()
c = re.sub(r"(?m)^AKHQ_ADMIN_PASSWORD=.*$", lambda m: "AKHQ_ADMIN_PASSWORD=" + p, c)
open("/root/akhq-credentials.txt", "w").write(c)
'
unset P; sudo systemctl restart akhq

To rotate the session-signing secret (every user is logged out):

sudo python3 -I -c '
import re, secrets
s = open("/etc/akhq/secrets.yml").read()
s = re.sub(r"(secret: \")[0-9a-f]{64}(\")", lambda m: m.group(1) + secrets.token_hex(32) + m.group(2), s, count=1)
open("/etc/akhq/secrets.yml", "w").write(s)
' && sudo systemctl restart akhq

The secret must stay 64 hexadecimal characters: AKHQ's configuration check refuses anything else, including AKHQ's well-known built-in default.

Step 11: Security checks

akhq-selfcheck re-proves the whole appliance with this instance's credentials: HTTPS, refusal of anonymous and HTTP Basic requests, the login with the right and a wrong password, the topic and consumer group APIs, that the session token is signed with this instance's own secret (and that a token signed with AKHQ's built-in default secret is refused), the redirect, refusal of other host names, cross-origin requests and WebSocket upgrades, the loopback-only ports, and the metadata block inside every service:

sudo akhq-selfcheck
AKHQ appliance self-check (https://<public-ip>/)
  [ok] HTTPS AKHQ login page -> 200
  [ok] topic API without a session -> 401; HTTP Basic (even with the right password) -> 401
  [ok] wrong password -> 303 back to the login page, no session cookie
  [ok] admin login with the per-instance password -> session; topic API -> 200 and lists demo-orders
  [ok] consumer-group API -> 200 and lists demo-consumer
  [ok] session token is HS256-signed with this instance's own secret (re-signed copy -> 200)
  [ok] tokens signed with AKHQ's built-in default secret or a random secret -> 401
  [ok] port 80 -> 301 https://<public-ip>/; unknown Host -> 444; CONNECT refused; cross-origin POST -> 403; WebSocket upgrade -> 403
  [ok] AKHQ (8080/28081) and Kafka (9092/9093) refuse the private address; AKHQ speaks no TLS (no SNI) itself
  [ok] instance metadata refused inside the akhq, kafka and nginx cgroups (loopback control 200)
SELFCHECK OK - HTTPS, per-instance login and session secret, origin guard, loopback-only AKHQ and Kafka, metadata block verified.

The self-check reads the demo broker's topics, so once you have switched the broker off (Step 8) it stops at that check. The metadata block is a systemd IPAddressDeny rule on each service; you can see it in effect:

for u in akhq kafka nginx akhq-firstboot; do printf '%s: ' $u; systemctl show -p IPAddressDeny --value $u.service; done
akhq: fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32
kafka: fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32
nginx: fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32
akhq-firstboot: fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32

Step 12: Versions, provenance and file locations

sed -n '1,6p' /usr/share/doc/akhq/BUILD-PROVENANCE.txt
/opt/akhq/jre/bin/java -version 2>&1 | sed -n 1p
ls /opt/kafka/libs/kafka-clients-*.jar
AKHQ 0.28.0 - official upstream release jar (not rebuilt)
source          https://github.com/tchiotludo/akhq/releases/download/0.28.0/akhq-0.28.0-all.jar
sha256          bb3c5f8ff52e67fa8b3324a6597664faa467dbe3b60c053e6d97304a6079b5ca  (GitHub release asset digest)
licence         Apache License 2.0 (LICENSE from the 0.28.0 tag, sha256 55b6cb57c1485b07a544fec6cce494e916682f36977e8dbb1620de91d6eeca86)
runtime         Eclipse Temurin 25.0.5+8 JRE (6fac98f0c8202efc3135a9e9e42aa61963ec42d1c5cbb40caf231e7b14a24ef6), /opt/akhq/jre, used by AKHQ only
netty-handler   4.2.13.Final (inside the jar)
openjdk version "25.0.5" 2026-10-20 LTS
/opt/kafka/libs/kafka-clients-4.4.0.jar

Known scanner finding. Vulnerability scanners report CVE-2026-75595 (GHSA-c4c3-7fpv-j4q5, a server name indication routing bypass in netty-handler) against the AKHQ 0.28.0 jar, which bundles netty-handler 4.2.13. It cannot be reached on this image: the flaw is in netty's TLS server name handling, and AKHQ here has no TLS listener at all. It serves plain HTTP on 127.0.0.1 only, nginx terminates TLS in front of it, and AKHQ's configuration check refuses to start if TLS is ever switched on inside AKHQ. The fix is merged upstream (AKHQ pull request #3351) and arrives with the next AKHQ release, which cloudimg will publish as a new version of this listing.

Path What it is
/etc/akhq/application.yml AKHQ settings and your cluster connections
/etc/akhq/secrets.yml session-signing secret and users (written at first boot)
/root/akhq-credentials.txt URL and admin password (root only)
/etc/kafka/server.properties, /var/lib/kafka/data demo broker settings and data
/etc/nginx/sites-available/cloudimg-akhq the HTTPS front door; certificate and key in /etc/nginx/ssl/
/usr/share/doc/akhq/ LICENSE and BUILD-PROVENANCE.txt
journalctl -u akhq AKHQ's log (also kafka, nginx, akhq-firstboot)

Step 13: Backup and maintenance

  • AKHQ keeps no data of its own: back up /etc/akhq (your connections, users and secret), /etc/cloudimg and /etc/nginx/ssl. The demo broker's data is disposable.
  • Ubuntu security updates install automatically (unattended-upgrades), including the OpenJDK runtime the demo broker uses. AKHQ and its Java runtime live in /opt/akhq; to upgrade, launch the newer version from the listing and copy /etc/akhq across.
  • AKHQ and the demo broker each run with a bounded Java heap (768 MB and 1 GB). An m5.large is comfortable; for large clusters with many topics give AKHQ more heap in /etc/default/akhq (JAVA_OPTS) and a larger instance.

Step 14: Security recommendations

  • Limit TCP 443 in the security group to the networks your users browse from, and TCP 22 to your admin network. AKHQ can change and delete topics, offsets and data on the clusters it manages; treat it as an administrative console, ideally reachable over a VPN or a private load balancer only.
  • Serve AKHQ under your own DNS name with a certificate from a trusted CA (Step 9).
  • Change the admin password if you share the credentials file, give other people their own users, and use read-only groups where you can (Step 10).
  • Connect to your clusters with SASL/SCRAM or mutual TLS over TLS (Step 7), with a dedicated Kafka user whose ACLs match what AKHQ users should be able to do.
  • Keep AKHQ on 127.0.0.1; publish nothing but nginx.

Support and licensing

cloudimg provides 24/7 technical support for this image by email and live chat: support@cloudimg.co.uk. AKHQ is licensed under the Apache License 2.0 (its LICENSE file is on the instance in /usr/share/doc/akhq), Apache Kafka under the Apache License 2.0, and Eclipse Temurin and OpenJDK under the GNU General Public License version 2 with the Classpath Exception. Apache Kafka is a trademark of the Apache Software Foundation, and AKHQ is a trademark of its respective owner; cloudimg is not affiliated with or endorsed by the Apache Software Foundation or the AKHQ project.