ActiveMQ Classic ships with a web console on port 8161 and well-known default credentials: admin/admin. Combined with a management interface reachable from the network, that equals full broker control for anyone who finds the port: browse and purge queues, move messages, read message bodies, create destinations, and in some versions reach the Jolokia JMX REST API for much worse.
Newer releases have improved the defaults. Since 5.16.x and 5.18.x, the embedded Jetty console binds to 127.0.0.1 and the remote JMX connector is disabled (createConnector="false") out of the box. These defaults are routinely undone: operators reconfigure the bind address for remote management, container images override it, and older brokers never had the protection. A large share of production brokers still expose 8161 with credentials that are in every scanner’s wordlist.
This guide is an audit and lockdown procedure: determine what your brokers actually expose, close the hole without losing the ability to operate, and monitor so it stays closed.
Why this exposure is worse than it looks
The web console is not a read-only dashboard. An authenticated admin session can browse message bodies (which often contain business data or credentials from upstream systems), purge queues, delete destinations, and send arbitrary messages. Through the Jolokia endpoint under /api/jolokia, an authenticated user can invoke JMX operations remotely, which is a short path to code execution on the broker host.
The exposure compounds with deserialization and RCE CVEs that require valid credentials or network reachability as a precondition:
- CVE-2023-46604: critical RCE in OpenWire ClassInfo deserialization (CVSS 10.0), actively exploited in the wild. Patched in 5.15.16, 5.16.7, 5.17.6, and 5.18.3. An exposed OpenWire port (61616) is its own emergency; an exposed console with admin/admin is how attackers enumerate and validate targets.
- CVE-2015-5254: Commons Collections deserialization RCE. Mitigate by whitelisting
org.apache.activemq.SERIALIZABLE_PACKAGES. - Recent advisories (for example, unauthenticated
/api/*contexts in early 6.x releases, and Jolokia-based RCE requiring valid credentials) keep the same shape: the attack is trivial when the console is reachable and the password is admin.
The broker’s management plane is a separate attack surface from its data plane, and both need explicit scoping.
flowchart LR attacker[Network attacker] -->|8161 with admin/admin| console[Jetty web console] attacker -->|61616 OpenWire| broker[Broker core] console -->|browse, purge, send| broker console -->|/api/jolokia JMX ops| jmx[JMX / MBeans] jmx -->|invoke operations| host[Broker host code exec] broker -->|deserialization CVEs| host
Step 1: map what is actually listening
Do not trust the config file. Trust the socket table. Run this on every broker host:
# What is listening on management and transport ports, and on which address
ss -tlnp | grep -E ':(8161|1099|61616|5672|61613|1883|61614)'
# Who is currently connected to the management interfaces
ss -tn state established '( sport = :8161 )'
ss -tn state established '( sport = :1099 )'
What you are looking for:
| Finding | Meaning | Risk |
|---|---|---|
127.0.0.1:8161 | Console bound to localhost only | Low; matches 5.16+/5.18.x default |
0.0.0.0:8161 or a routable IP | Console reachable from the network | High until credentials and network policy are verified |
0.0.0.0:1099 | Remote JMX connector enabled | High; remote JMX without TLS and auth is arbitrary code execution territory |
0.0.0.0:61616 | OpenWire reachable from network | Expected for clients, but must be patched against CVE-2023-46604 and authenticated |
If the broker runs in a container, check the published ports on the host too. ss inside the container shows the bind, but the host’s port mapping determines real reachability:
docker ps --format 'table {{.Names}}\t{{.Ports}}' | grep activemq
Some container images also override the localhost bind: the official ActiveMQ Classic 6.x image sets -Djetty.host=0.0.0.0, making the console remotely reachable by default in Docker even though a bare-metal install of the same version would not be.
Also test reachability from a host that should not have access. A bind to 0.0.0.0 behind a security group that only allows the management VLAN is a different risk than the same bind open to the whole corporate network.
Step 2: test the credentials
From an allowed management host:
# Does the console answer with default credentials?
curl -s -o /dev/null -w '%{http_code}\n' \
-u admin:admin http://<broker-host>:8161/admin/
# Does Jolokia answer with default credentials? (read-only query)
curl -s -u admin:admin \
'http://<broker-host>:8161/api/jolokia/read/org.apache.activemq:type=Broker,brokerName=localhost/BrokerId'
A 200 on either, using admin/admin, is a finding. A 200 from a host outside your management network is an incident: rotate the credentials, review access logs, and check for destinations, consumers, or network connectors you did not create.
Console credentials live in conf/jetty-realm.properties in a simple username: password, role format. Broker transport authentication (OpenWire and friends) is separate, typically configured in activemq.xml via a JAAS or simple authentication plugin, and it commonly also ships with admin/admin. Audit both planes. They are independent: fixing the console does nothing for port 61616 and vice versa.
While you are in the config, check the two defaults that matter most:
# Jetty bind host in conf/jetty.xml (absence or 0.0.0.0 means network-reachable)
grep -n -A2 'jettyPort' conf/jetty.xml | grep -i host
# Remote JMX connector in conf/activemq.xml
grep -n 'createConnector' conf/activemq.xml
On 5.16+/5.18.x defaults, Jetty has a localhost bind and the management context has createConnector="false". If either line says otherwise, someone opened it deliberately or it was never closed.
Step 3: lock it down
In order of preference:
1. Keep management on localhost or a management VLAN. If operators need the console remotely, reach it through an SSH tunnel (ssh -L 8161:localhost:8161 broker-host) or a jump host rather than binding Jetty to 0.0.0.0. If a network bind is unavoidable, restrict it to a specific management interface IP in conf/jetty.xml and enforce a host firewall or security group rule that allows only known management sources.
2. Change the default credentials on both planes. Edit conf/jetty-realm.properties for the console and the authentication plugin in activemq.xml for transports. Use unique per-broker credentials so one leaked password is not a fleet-wide compromise. A broker restart is required for config changes; plan it like any other restart, and in shared-storage HA remember the standby will acquire the store lock while the active is down.
3. Disable remote JMX unless you need it. Keep createConnector="false". For monitoring, prefer local JMX collection or a read-only Jolokia policy over opening 1099 to the network.
4. Restrict what authenticated users can do. Default deployments historically left low-privilege web users able to reach /admin/* and Jolokia broker-management operations. Patch to current versions and scope roles so only genuine admin accounts hold the admin role.
5. Turn the console off if nobody uses it. If all operations go through JMX, CLI, or config management, removing or not starting the web console webapp eliminates the surface entirely. Verify nothing in your runbooks depends on it first.
6. Patch. An exposed OpenWire port on an unpatched broker is exploitable regardless of console hygiene. Confirm you are on or past the CVE-2023-46604 fix lines (5.15.16, 5.16.7, 5.17.6, 5.18.3 or later in each train).
Step 4: verify and keep it closed
After changes:
# Bind addresses are what you intended
ss -tlnp | grep -E ':(8161|1099)'
# Default credentials now fail
curl -s -o /dev/null -w '%{http_code}\n' -u admin:admin http://localhost:8161/admin/
# New credentials work from an allowed source only
curl -s -o /dev/null -w '%{http_code}\n' -u <user>:<pass> http://localhost:8161/admin/
Expect 401/403 for the default creds and a refusal or timeout from non-management hosts. Then add permanent tripwires:
| Signal | Why it matters | Warning sign |
|---|---|---|
| Established connections to 8161/1099 | Management access should only come from known hosts | Any session from a non-allowlisted source |
| Authentication failure rate in the broker log | Default creds mean scanners will knock | Sustained failures, or failures from multiple source IPs with different usernames |
| Authorization denial log lines | Authenticated user probing beyond its role | Repeated not authorized entries for admin operations |
| Unexpected destination creation | Auto-create on first use plus broad access lets attackers or rogue clients shape the broker | Destinations outside expected naming patterns |
| Bind/config drift | The hole is usually reopened by a well-meaning change | 8161 or 1099 suddenly listening on 0.0.0.0 |
Treat any access to a supposedly localhost-only interface from an untrusted network as page-worthy. Sporadic auth failures from a single source are a ticket; a broad failure burst right after a credential rotation usually means a client you forgot to update, not an attack.
Common pitfalls
- Trusting
conf/jetty.xmloverss. Container entrypoints, systemd drop-ins, and-Djetty.hoston the command line all override what the XML suggests. The socket table is the truth. - Fixing the console and forgetting 61616. Transport authentication is a separate config with its own defaults. Attackers do not care which plane they enter through.
- Rotating credentials without checking what breaks. Monitoring that authenticates to Jolokia with admin/admin will fail after rotation. Update collectors at the same time.
- Assuming HA standby is safe. Both brokers in a pair run Jetty. Audit both.
- Reopening the bind for a one-off debug session and never closing it. This is how most exposures happen. If you open it, put the close step in the same change ticket.
How Netdata helps
- Netdata’s per-second network and socket visibility on the broker host makes a surprise listener or an unexpected established session on 8161 or 1099 visible immediately, rather than at the next quarterly audit.
- Correlating connection spikes to management ports with authentication failure patterns in the broker log distinguishes a scanner from a broken client after credential rotation.
- Broker-level signals (destination count, consumer count, connection count) act as integrity checks: an intruder with console access eventually moves one of these, and anomaly detection on them catches what the access logs miss.
- Alerting on unexpected destination creation and auth failure bursts turns the lockdown from a point-in-time exercise into a continuously verified state.
Related guides
- ActiveMQ advisory topics: hidden destinations consuming resources
- ActiveMQ broker down: telling a crashed broker from a hung one
- ActiveMQ broker won’t start: port conflicts, store recovery, and lock contention
- ActiveMQ InactivityIOException: Channel was inactive for too long
- ActiveMQ connection and session leak: clients that never close
- ActiveMQ consumers connected but not acknowledging: the zombie consumer
- ActiveMQ destination explosion: dynamic destinations, MBean bloat, and GC pressure
- ActiveMQ disk full on the KahaDB partition: write failures and store corruption risk
- ActiveMQ.DLQ growing: dead letter queue accumulation and poison messages
- ActiveMQ DLQ never expires: setting TTL so the dead-letter queue stops leaking storage
- ActiveMQ offline durable subscriber pending messages: the silent storage leak
- ActiveMQ enqueue outpacing dequeue: reading the rate imbalance before the backlog






