Kroxylicious
Overview
Kroxylicious is an open-source, protocol-aware proxy for Apache Kafka. It sits between your Kafka clients and your Kafka brokers and understands the Kafka wire protocol itself, so it can inspect, rewrite and police traffic as it passes. Because the policy lives in the proxy rather than in each application, you can apply encryption, multi-tenancy, filtering and access policy across every client at once, without changing a line of client code.
This guide covers deploying and configuring Kroxylicious on Ubuntu 24.04 on Azure with the SecureAnyCloud Azure Marketplace image.
A proxy needs a broker behind it, so this image is self-contained. It ships Kroxylicious 0.23.0 alongside a working single-node Apache Kafka 4.3.1 broker running in KRaft mode, with no ZooKeeper. You can produce and consume through the proxy as soon as the VM finishes booting. When you are ready to put the proxy in front of your own cluster, you change one line of configuration. Both products are installed from the official upstream release artifacts, pinned and verified by SHA-512 at install time.
The broker is reachable only through the proxy. The backing Kafka broker binds to loopback only, on ports 9092 and 9093, so nothing outside the VM can open a socket to it. Kroxylicious is the single network-facing entry point, on port 9192 for bootstrap and 9193 for the broker node. This topology is deliberate: a proxy that can be trivially bypassed enforces nothing.
Secure by default, with no shipped credential. Client-facing authentication is SASL SCRAM-SHA-512, enforced twice. The backing broker requires SASL. Kroxylicious also runs the SaslInspection filter with requireAuthentication set to true, so the proxy refuses to forward any request other than the authentication exchange until authentication succeeds.
The SCRAM credential is generated uniquely on each VM at first boot and written straight into Kafka cluster metadata at format time. The image contains no default or bootstrap password, and no published credential is ever live.
What you get:
- Kroxylicious 0.23.0 from the official GitHub release (SHA-512 verified), run under systemd as
kroxylicious.service - Apache Kafka 4.3.1 (Scala 2.13) in KRaft mode as the backing broker, run under systemd as
kafka.serviceand bound to loopback only - Client-facing SASL SCRAM-SHA-512 authentication, enforced at both the proxy and the broker
- A per-VM SCRAM credential generated at first boot and written to
/stage/scripts/kroxylicious-credentials.logwith mode 0600 - A pre-created demo topic called SecureAnyCloud, created through the proxy on first boot
- A Prometheus metrics endpoint for the proxy on loopback
127.0.0.1:9190 - An on-image self-test,
/usr/local/sbin/kroxylicious-selftest.sh, that proves end-to-end traffic really traverses the proxy - OpenJDK 21, required by Kroxylicious 0.23.0 and supported by Kafka 4.3
Prerequisites
- An active Azure subscription, an SSH public key, and a VNet and subnet in your target region
- A subscription to the Kroxylicious listing on Azure Marketplace
- Network Security Group rules that allow TCP 22 from your admin network. Kafka clients inside the VNet need TCP 9192 and 9193 to the VM.
Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM).
This image runs two JVMs, so both heaps are sized explicitly for that shape. Kafka is capped at 896 MB and Kroxylicious at 640 MB, with the proxy's direct memory capped at 192 MB. A freshly booted VM has roughly 2.9 GB still available. Choose a larger size for heavy sustained throughput or to point the proxy at a large external cluster.
Deploy from the Azure Portal
- Open the Kroxylicious offer on the Azure Marketplace and choose Get It Now, then Create.
- Select your subscription, resource group and region, and choose the
Standard_B2ssize. - Provide your SSH public key for the
azureuseraccount. - In the Network Security Group, allow inbound TCP 22 from your admin network, and TCP 9192 and 9193 from the subnet your Kafka clients run in.
- Create the VM.
Deploy from the Azure CLI
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:kroxylicious-ubuntu-24-04:default:latest
az vm create \
--resource-group my-rg \
--name kroxylicious-01 \
--image sac1647283583153:kroxylicious-ubuntu-24-04:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku StandardWait for first boot and connect
On the first boot only, a one-shot unit called kroxylicious-firstboot.service runs before either product service starts. It does four things:
- Generates this VM's unique SCRAM credential.
- Writes the credential directly into Kafka cluster metadata as the storage is formatted.
- Resolves the address the proxy will advertise to clients.
- Writes the credentials file.
Both kafka.service and kroxylicious.service are gated on the marker this unit creates when it finishes, so neither can start against half-prepared state.
First boot completes in a few seconds. Connect with SSH:
ssh azureuser@<vm-ip>Confirm both services are running
Check that both services are active:
sudo systemctl is-active kroxylicious.service kafka.serviceBoth report active. Then check the listening topology, because it is what makes this deployment safe:
sudo ss -ltn | grep -E ':(9092|9093|9190|9192|9193)'The proxy listens on *:9192 and *:9193. The broker appears only on 127.0.0.1:9092 and 127.0.0.1:9093, and the proxy's metrics endpoint only on 127.0.0.1:9190. Any traffic that reaches Kafka from off the VM must therefore pass through Kroxylicious.
Read this VM's credentials
The per-VM SCRAM credential is written at first boot to a file that only root can read. Print it:
sudo cat /stage/scripts/kroxylicious-credentials.logThe file records the bootstrap address clients should use, the SASL user SecureAnyCloud, this VM's generated password, and the security protocol and mechanism.
A ready-made client configuration containing the same credential is also written to /etc/kroxylicious/client.properties, root-owned with mode 0600. The examples below use it directly.
Produce and consume through the proxy
The image pre-creates a topic called SecureAnyCloud. Produce a message to it through the proxy on port 9192:
echo "hello from sac" | sudo /opt/kafka/bin/kafka-console-producer.sh \
--bootstrap-server 127.0.0.1:9192 \
--command-config /etc/kroxylicious/client.properties \
--topic sacRead the message back, again through the proxy:
sudo timeout 40 /opt/kafka/bin/kafka-console-consumer.sh \
--bootstrap-server 127.0.0.1:9192 \
--command-config /etc/kroxylicious/client.properties \
--topic sac --from-beginning --max-messages 1--max-messages 1 makes the consumer exit cleanly as soon as it has read one message. Without it, the consumer waits for a timeout and then reports a TimeoutException. This is normal behaviour, but it looks like a fault.
On Kafka 4, the console tools take --command-config. The older --producer.config and --consumer.config flags are deprecated and print warnings that corrupt captured output.
Next, confirm that the client is steered to the proxy rather than to the broker. Ask the cluster which nodes it advertises:
sudo /opt/kafka/bin/kafka-broker-api-versions.sh \
--bootstrap-server 127.0.0.1:9192 \
--command-config /etc/kroxylicious/client.properties | awk 'NR==1'The node reported is the proxy's node port, 9193, on this VM's advertised address. It is never the broker's 9092. Kroxylicious rewrites the cluster metadata as it passes, which keeps clients on the proxy for the whole session rather than only for the initial bootstrap.
Verify the authentication wall
Client-facing authentication is on by default. A client that presents the wrong password is rejected during the SASL exchange. Check the filter configuration:
sudo grep -E 'type: SaslInspection|enabledMechanisms|requireAuthentication' /etc/kroxylicious/config.yamlrequireAuthentication is set to true, so Kroxylicious forwards nothing beyond the authentication exchange until a client has authenticated successfully. The backing broker independently enforces SASL SCRAM-SHA-512 as well, so removing the proxy filter would not open the cluster.
Run the built-in self-test
The image ships a self-test that proves the deployment end to end, rather than merely checking that a process is running:
sudo /usr/local/sbin/kroxylicious-selftest.shThe self-test runs these checks in order:
- It produces a uniquely tagged message through the proxy and consumes it back, asserting that the payload matches byte for byte.
- It reads the proxy's own upstream request counter and asserts that it moved. This distinguishes traffic that traversed the proxy from traffic that reached the broker directly.
- It confirms that the client is steered to the proxy node port.
- It checks that a wrong credential is explicitly rejected.
- It stops the proxy and confirms that the round trip breaks.
- It restarts the proxy and proves the round trip again, so a passing negative result cannot be an artefact of a broken system.
The script exits non-zero if any check fails.
Prometheus metrics
Kroxylicious publishes Prometheus metrics on loopback port 9190. List a sample of the proxy metrics:
curl -s http://127.0.0.1:9190/metrics | awk '/^kroxylicious_(client_to_proxy|proxy_to_server)/ && ++n <= 8'The client_to_proxy families count what clients sent to the proxy. The proxy_to_server families count what the proxy forwarded upstream to the broker. Comparing the two is the most direct way to confirm that traffic flows through the proxy in production.
To scrape these metrics from another host, bind the management endpoint to a routable address by editing bindAddress under management in /etc/kroxylicious/config.yaml. Restrict access with a Network Security Group rule.
Point the proxy at your own Kafka cluster
The bundled broker makes the image useful immediately. In production, you will usually put Kroxylicious in front of a cluster you already run, which is a single configuration change.
Edit /etc/kroxylicious/config.yaml, set bootstrapServers under targetCluster to your own cluster, then restart the proxy.
- If your cluster has more than one broker, widen
nodeIdRangesso that every broker node id maps to a proxy port. Open those ports in your Network Security Group. - If your cluster requires TLS or a different SASL mechanism upstream, add the matching
tlsand authentication stanzas totargetCluster.
Once the proxy serves your own cluster, you can stop and disable the bundled broker with sudo systemctl disable --now kafka.service. The bundled broker only ever listened on loopback, so leaving it running is not an exposure. Disabling it returns roughly 370 MB of RAM to the proxy.
Expose the proxy beyond the VNet
By default, the proxy advertises this VM's private address to clients. This is deliberate. A Kafka proxy that terminates client connections should not be reachable from the internet by default, and this default keeps the deployment safe until you make an explicit decision.
To serve clients from outside the VNet, set the advertised address to a name or address that those clients can reach, then restart the proxy:
sudo sed -i 's|advertisedBrokerAddressPattern: .*|advertisedBrokerAddressPattern: <your-domain>|' \
/etc/kroxylicious/config.yaml
sudo systemctl restart kroxylicious.serviceReplace <your-domain> with the hostname your clients will use, for example kafka.internal.example.com.
Use a DNS name rather than a bare public IP. A DNS name survives address changes, and you need one anyway when you add TLS. Whichever you choose, the value must be the address the client will use, because it is handed to clients in the cluster metadata.
Also open TCP 9192 and 9193 in the Network Security Group. Restrict the source range to the networks your clients run in rather than leaving it open to the internet.
Record encryption and the other filters
Kroxylicious ships more filters than the SASL inspection filter enabled here. They include record encryption, multi-tenancy, record validation, entity isolation and authorization. They are part of the distribution, and you enable them by adding them to filterDefinitions and defaultFilters in /etc/kroxylicious/config.yaml.
Record encryption is deliberately not enabled in this image. The filter encrypts record payloads transparently, but it needs a key management service to hold the key material. Every production backend it supports (Azure Key Vault, AWS KMS, HashiCorp Vault, Fortanix DSM and Thales CipherTrust) is an external service. The only backend that runs standalone on a single VM is intended for testing and holds key material in the proxy's memory.
Shipping that backend would mean advertising encryption backed by a test double, so the image ships the filter unconfigured. As a result, there is no encryption key material in this image and nothing sensitive baked into it.
On Azure, the natural choice is the Azure Key Vault provider. Create a vault and a key, and grant this VM's managed identity permission to use it. Then add the record encryption filter to filterDefinitions, referencing that vault. See the Kroxylicious documentation for the configuration schema that matches your version.
Server components
- Kroxylicious 0.23.0: /opt/kroxylicious
- Apache Kafka 4.3.1 (Scala 2.13): /opt/kafka
- OpenJDK 21: /usr/lib/jvm
Network ports
- 22 (all interfaces): SSH administration
- 9192 (all interfaces): Kroxylicious bootstrap, the address clients connect to
- 9193 (all interfaces): Kroxylicious broker node port for broker node id 1
- 9190 (loopback): Kroxylicious management and Prometheus metrics
- 9092 (loopback): backing Kafka broker, not reachable off the VM
- 9093 (loopback): Kafka KRaft controller, not reachable off the VM
Scripts and log files
- /etc/kroxylicious/config.yaml: proxy configuration (virtual cluster, gateway, filters)
- /etc/kroxylicious/client.properties: ready-made SASL client configuration for this VM
- /stage/scripts/kroxylicious-credentials.log: this VM's generated credentials, mode 0600
- /usr/local/sbin/kroxylicious-selftest.sh: end-to-end proof that traffic traverses the proxy
- /usr/local/sbin/kroxylicious-firstboot.sh: first-boot credential and address setup
- /var/log/sac/sac-firstboot.log: first-boot log
- /opt/kafka/config/server.properties: backing broker configuration
- /etc/default/kroxylicious and /etc/default/kafka: JVM heap sizing for each service
Manage the services
Check the status, restart the proxy and read its recent logs:
sudo systemctl status kroxylicious.service
sudo systemctl restart kroxylicious.service
sudo journalctl -u kroxylicious.service -n 50 --no-pagerThe same commands work for kafka.service. The two services are ordered but not tightly coupled, so restarting the proxy does not disturb the broker. You can restart the proxy on its own while you iterate on filter configuration.
Troubleshooting
Clients connect but then time out
The cause is almost always the advertised address. Clients bootstrap against port 9192, then reconnect to whatever address the proxy advertises. If the client cannot reach that address, the bootstrap succeeds and everything afterwards hangs.
Check what is advertised with sudo grep advertisedBrokerAddressPattern /etc/kroxylicious/config.yaml, and confirm that the client can reach that address on port 9193. See Step 11.
Authentication failures
Confirm that the client uses SASL SCRAM-SHA-512 over SASL_PLAINTEXT, and that its password matches the one in /stage/scripts/kroxylicious-credentials.log.
The password is unique to this VM and is not recoverable if the credentials file is deleted. You can still create additional SCRAM users with kafka-configs.sh against the broker on loopback.
A service will not start
Both product services are gated on the first-boot marker at /var/lib/sac/data/kroxylicious-firstboot.done. If that file is missing, the services stay inactive by design rather than start against unprepared state. Run sudo journalctl -u kroxylicious-firstboot.service to see why first boot did not finish.
Memory pressure
This image runs two JVMs on a 4 GB VM. If you have raised throughput or pointed the proxy at a large cluster, check free -m. Then either increase the VM size or disable the bundled broker as described in Step 10. The heaps are set in /etc/default/kroxylicious and /etc/default/kafka.
Security Recommendations
- Restrict the proxy ports. Limit TCP 9192 and 9193 in the Network Security Group to the subnets your Kafka clients run in. Do not expose them to the internet.
- Keep the broker on loopback. Binding it to a routable address would let clients bypass the proxy and everything the proxy enforces.
- Add TLS. Enable TLS on the client-facing gateway before you carry production data across any untrusted network. Kroxylicious terminates TLS on the gateway and can use a separate TLS configuration upstream to the broker.
- Create per-application users. Create per-application SCRAM users rather than sharing the built-in SecureAnyCloud credential, and use the authorization filter to constrain what each identity may do.
- Keep the VM patched. Unattended upgrades are enabled in the image.
Support and Licensing
Kroxylicious and Apache Kafka are open-source software, both licensed under the Apache License 2.0. SecureAnyCloud packages, hardens and supports the image. Your use of the software remains subject to the upstream licences.
For assistance, contact SecureAnyCloud support:
- Email: sales@secureanycloud.com
- Response time: 24/7, with a guaranteed 24-hour response SLA
- Website: https://secureanycloud.com
Deploy on Azure
Launch Kroxylicious 0.23.0 on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com