Set up comprehensive Apache Tomcat 10 monitoring using JMX metrics, Prometheus JMX Exporter, and Grafana dashboards to track application performance, memory usage, and request metrics in real-time.
Prerequisites
- Root or sudo access
- Java 11 or higher
- 4GB RAM minimum
- Port 8080, 9090, and 3000 available
What this solves
Monitoring Apache Tomcat applications effectively requires collecting JMX (Java Management Extensions) metrics and visualizing them in a centralized dashboard. This tutorial shows you how to configure Tomcat with JMX monitoring, deploy the Prometheus JMX Exporter, and create Grafana dashboards for tracking memory usage, request processing, thread pools, and application performance metrics.
Step-by-step installation
Install and configure Java Development Kit
Tomcat requires Java 11 or higher. Install OpenJDK and verify the installation.
sudo apt update
sudo apt install -y openjdk-11-jdk
java -version
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
echo 'export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64' >> ~/.bashrc
Install Apache Tomcat 10
Download and install Tomcat 10 with proper user permissions and directory structure.
sudo useradd -r -m -U -d /opt/tomcat -s /bin/false tomcat
sudo wget -O /tmp/tomcat-10.1.25.tar.gz https://archive.apache.org/dist/tomcat/tomcat-10/v10.1.25/bin/apache-tomcat-10.1.25.tar.gz
sudo tar -xzf /tmp/tomcat-10.1.25.tar.gz -C /opt/tomcat --strip-components=1
sudo chown -R tomcat:tomcat /opt/tomcat
sudo chmod +x /opt/tomcat/bin/*.sh
Configure Tomcat JMX monitoring
Enable JMX remote monitoring by configuring authentication and SSL settings for secure metrics collection.
jmx-user readonly
monitor readwrite
jmx-user jmx_password_456
monitor monitor_password_789
sudo chown tomcat:tomcat /opt/tomcat/conf/jmxremote.*
sudo chmod 600 /opt/tomcat/conf/jmxremote.password
sudo chmod 644 /opt/tomcat/conf/jmxremote.access
Configure Tomcat startup with JMX options
Set JMX system properties to enable remote monitoring on port 9999 with authentication.
#!/bin/bash
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote"
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.port=9999"
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.rmi.port=9999"
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.ssl=false"
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.authenticate=true"
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.password.file=/opt/tomcat/conf/jmxremote.password"
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.access.file=/opt/tomcat/conf/jmxremote.access"
JAVA_OPTS="$JAVA_OPTS -Djava.rmi.server.hostname=127.0.0.1"
JAVA_OPTS="$JAVA_OPTS -Xms512m -Xmx2048m"
sudo chmod +x /opt/tomcat/bin/setenv.sh
sudo chown tomcat:tomcat /opt/tomcat/bin/setenv.sh
Create Tomcat systemd service
Configure Tomcat to run as a system service with proper resource limits and monitoring capabilities.
[Unit]
Description=Apache Tomcat 10 Web Application Container
Wants=network.target
After=network.target
[Service]
Type=forking
Environment=JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
Environment=CATALINA_PID=/opt/tomcat/temp/tomcat.pid
Environment=CATALINA_HOME=/opt/tomcat
Environment=CATALINA_BASE=/opt/tomcat
Environment='CATALINA_OPTS=-Xms512M -Xmx2048M -server -XX:+UseParallelGC'
Environment='JAVA_OPTS=-Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom'
ExecStart=/opt/tomcat/bin/startup.sh
ExecStop=/opt/tomcat/bin/shutdown.sh
User=tomcat
Group=tomcat
UMask=0007
RestartSec=10
Restart=always
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable tomcat
sudo systemctl start tomcat
Install Prometheus JMX Exporter
Download and configure the Prometheus JMX Exporter to collect Tomcat metrics and expose them for Prometheus scraping.
sudo mkdir -p /opt/prometheus
cd /opt/prometheus
sudo wget https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/0.20.0/jmx_prometheus_javaagent-0.20.0.jar
sudo chown tomcat:tomcat /opt/prometheus/jmx_prometheus_javaagent-0.20.0.jar
rules:
# Tomcat metrics
- pattern: 'Catalina<>(.+):'
name: tomcat_$3_total
labels:
port: "$1"
protocol: "$2"
help: Tomcat global $3
type: COUNTER
- pattern: 'Catalina[-a-zA-Z0-9+&@#/%=~_|]), name=([-a-zA-Z0-9+/$%~_-|!.]), J2EEApplication=none, J2EEServer=none><>(requestCount|maxTime|processingTime|errorCount):'
name: tomcat_servlet_$3_total
labels:
module: "$1"
servlet: "$2"
help: Tomcat servlet $3 total
type: COUNTER
- pattern: 'Catalina<>(.+):'
name: tomcat_threads_$3_total
labels:
port: "$1"
protocol: "$2"
help: Tomcat $3
type: GAUGE
- pattern: 'Catalina[-a-zA-Z0-9+&@#/%=~_|]), context=([-a-zA-Z0-9+/$%~_-|!.])><>(processingTime|sessionCounter|rejectedSessions|expiredSessions):'
name: tomcat_session_$3_total
labels:
host: "$1"
context: "$2"
help: Tomcat session $3 total
# JVM metrics
- pattern: 'java.lang<>HeapMemoryUsage.(.+):'
name: jvm_memory_heap_$1
type: GAUGE
help: JVM heap memory usage
- pattern: 'java.lang<>NonHeapMemoryUsage.(.+):'
name: jvm_memory_nonheap_$1
type: GAUGE
help: JVM non-heap memory usage
- pattern: 'java.lang<>(.+):'
name: jvm_gc_$2
labels:
collector: "$1"
type: GAUGE
help: JVM garbage collection
sudo chown tomcat:tomcat /opt/prometheus/tomcat-config.yaml
Update Tomcat configuration for JMX Exporter
Modify the Tomcat service to include the JMX Exporter Java agent for automatic metrics collection.
#!/bin/bash
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote"
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.port=9999"
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.rmi.port=9999"
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.ssl=false"
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.authenticate=true"
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.password.file=/opt/tomcat/conf/jmxremote.password"
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.access.file=/opt/tomcat/conf/jmxremote.access"
JAVA_OPTS="$JAVA_OPTS -Djava.rmi.server.hostname=127.0.0.1"
JAVA_OPTS="$JAVA_OPTS -javaagent:/opt/prometheus/jmx_prometheus_javaagent-0.20.0.jar=8080:/opt/prometheus/tomcat-config.yaml"
JAVA_OPTS="$JAVA_OPTS -Xms512m -Xmx2048m"
sudo systemctl restart tomcat
sudo systemctl status tomcat
Install Prometheus
Install Prometheus to collect metrics from the JMX Exporter endpoint running on port 8080.
sudo apt install -y prometheus
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
# - "first_rules.yml"
# - "second_rules.yml"
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'tomcat-jmx'
static_configs:
- targets: ['localhost:8080']
scrape_interval: 10s
metrics_path: /metrics
params:
format: [prometheus]
sudo systemctl restart prometheus
sudo systemctl enable prometheus
Install and configure Grafana
Install Grafana for creating monitoring dashboards and visualization of Tomcat metrics.
wget -q -O - https://packages.grafana.com/gpg.key | sudo apt-key add -
echo "deb https://packages.grafana.com/oss/deb stable main" | sudo tee -a /etc/apt/sources.list.d/grafana.list
sudo apt update
sudo apt install -y grafana
sudo systemctl enable grafana-server
sudo systemctl start grafana-server
Configure Grafana data source
Add Prometheus as a data source in Grafana to enable querying Tomcat metrics for dashboard creation.
curl -X POST \
http://admin:admin@localhost:3000/api/datasources \
-H 'Content-Type: application/json' \
-d '{
"name": "Prometheus",
"type": "prometheus",
"url": "http://localhost:9090",
"access": "proxy",
"basicAuth": false,
"isDefault": true
}'
Create Tomcat monitoring dashboard
Import a comprehensive Tomcat dashboard that displays JVM memory, thread pools, request metrics, and garbage collection statistics.
{
"dashboard": {
"id": null,
"title": "Tomcat JMX Monitoring",
"tags": ["tomcat", "jmx", "monitoring"],
"timezone": "browser",
"panels": [
{
"id": 1,
"title": "JVM Heap Memory Usage",
"type": "stat",
"targets": [
{
"expr": "jvm_memory_heap_used / jvm_memory_heap_max * 100",
"legendFormat": "Heap Usage %"
}
],
"gridPos": {"h": 8, "w": 6, "x": 0, "y": 0}
},
{
"id": 2,
"title": "Active Threads",
"type": "graph",
"targets": [
{
"expr": "tomcat_threads_currentThreadCount_total",
"legendFormat": "Current Threads"
},
{
"expr": "tomcat_threads_currentThreadsBusy_total",
"legendFormat": "Busy Threads"
}
],
"gridPos": {"h": 8, "w": 12, "x": 6, "y": 0}
},
{
"id": 3,
"title": "Request Processing Time",
"type": "graph",
"targets": [
{
"expr": "rate(tomcat_processingTime_total[5m])",
"legendFormat": "Processing Time Rate"
}
],
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 8}
},
{
"id": 4,
"title": "Request Count",
"type": "stat",
"targets": [
{
"expr": "tomcat_requestCount_total",
"legendFormat": "Total Requests"
}
],
"gridPos": {"h": 8, "w": 6, "x": 12, "y": 8}
}
],
"time": {
"from": "now-1h",
"to": "now"
},
"refresh": "30s"
}
}
curl -X POST \
http://admin:admin@localhost:3000/api/dashboards/db \
-H 'Content-Type: application/json' \
-d @/tmp/tomcat-dashboard.json
Configure firewall access
Open the required ports for Tomcat, Prometheus, and Grafana services while maintaining security.
sudo ufw allow 8080/tcp comment 'Tomcat JMX Exporter'
sudo ufw allow 8009/tcp comment 'Tomcat AJP'
sudo ufw allow 9090/tcp comment 'Prometheus'
sudo ufw allow 3000/tcp comment 'Grafana'
sudo ufw reload
Verify your setup
Test the monitoring stack by checking service status and verifying metrics collection.
# Check Tomcat status and JMX metrics endpoint
sudo systemctl status tomcat
curl -s http://localhost:8080/metrics | head -20
Verify Prometheus is scraping Tomcat metrics
curl -s 'http://localhost:9090/api/v1/query?query=up{job="tomcat-jmx"}' | jq '.data.result[0].value[1]'
Check Grafana service
sudo systemctl status grafana-server
Test JMX direct connection
echo "Available MBeans:" | jconsole localhost:9999
Access the web interfaces:
- Tomcat Manager: http://your-server-ip:8080/manager (admin/secure_password_123)
- Prometheus: http://your-server-ip:9090
- Grafana: http://your-server-ip:3000 (admin/admin)
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| JMX Exporter returns 404 | Agent not loaded or wrong port | Check /opt/tomcat/logs/catalina.out for startup errors |
| Prometheus shows target down | Firewall blocking or service not running | sudo systemctl restart tomcat and check port 8080 |
| No metrics in Grafana | Data source misconfigured | Verify Prometheus URL in Grafana data source settings |
| High memory usage | JVM heap size too small | Increase -Xmx value in setenv.sh and restart Tomcat |
| Permission denied errors | Incorrect file ownership | sudo chown -R tomcat:tomcat /opt/tomcat |
Next steps
- Monitor Node.js applications with Prometheus and Grafana for similar application monitoring patterns
- Configure Prometheus alerting with AlertManager notifications to set up alerts for Tomcat issues
- Implement Tomcat SSL certificates for production deployment
- Configure Tomcat clustering for high availability and load balancing
- Set up ELK stack for Tomcat log analysis and monitoring
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Tomcat JMX Monitoring Setup Script
# Usage: ./install-tomcat-jmx.sh [hostname]
SCRIPT_NAME="$(basename "$0")"
HOSTNAME="${1:-127.0.0.1}"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
print_status() {
echo -e "${GREEN}[INFO]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
usage() {
echo "Usage: $SCRIPT_NAME [hostname]"
echo " hostname: IP or hostname for JMX binding (default: 127.0.0.1)"
exit 1
}
cleanup() {
print_error "Installation failed. Cleaning up..."
systemctl stop tomcat 2>/dev/null || true
systemctl disable tomcat 2>/dev/null || true
rm -f /etc/systemd/system/tomcat.service
userdel -r tomcat 2>/dev/null || true
rm -rf /opt/tomcat /opt/prometheus
}
trap cleanup ERR
# Check prerequisites
if [[ $EUID -ne 0 ]]; then
print_error "This script must be run as root"
exit 1
fi
if [[ "$#" -gt 1 ]]; then
usage
fi
# Detect distribution
if [ ! -f /etc/os-release ]; then
print_error "Cannot detect Linux distribution"
exit 1
fi
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update"
JAVA_HOME="/usr/lib/jvm/java-11-openjdk-amd64"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf check-update || true"
JAVA_HOME="/usr/lib/jvm/java-11-openjdk"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum check-update || true"
JAVA_HOME="/usr/lib/jvm/java-11-openjdk"
;;
*)
print_error "Unsupported distribution: $ID"
exit 1
;;
esac
print_status "Detected distribution: $ID using $PKG_MGR"
# Step 1: Update package manager and install Java
print_status "[1/8] Installing Java Development Kit..."
$PKG_UPDATE
if [[ "$PKG_MGR" == "apt" ]]; then
$PKG_INSTALL openjdk-11-jdk wget tar
else
$PKG_INSTALL java-11-openjdk java-11-openjdk-devel wget tar
fi
# Verify Java installation
if ! java -version &>/dev/null; then
print_error "Java installation failed"
exit 1
fi
# Step 2: Create Tomcat user and install Tomcat
print_status "[2/8] Installing Apache Tomcat 10..."
useradd -r -m -U -d /opt/tomcat -s /bin/false tomcat 2>/dev/null || true
wget -O /tmp/tomcat-10.1.25.tar.gz https://archive.apache.org/dist/tomcat/tomcat-10/v10.1.25/bin/apache-tomcat-10.1.25.tar.gz
tar -xzf /tmp/tomcat-10.1.25.tar.gz -C /opt/tomcat --strip-components=1
chown -R tomcat:tomcat /opt/tomcat
chmod 755 /opt/tomcat
chmod +x /opt/tomcat/bin/*.sh
rm -f /tmp/tomcat-10.1.25.tar.gz
# Step 3: Configure JMX access files
print_status "[3/8] Configuring JMX access control..."
cat > /opt/tomcat/conf/jmxremote.access << 'EOF'
jmx-user readonly
monitor readwrite
EOF
cat > /opt/tomcat/conf/jmxremote.password << 'EOF'
jmx-user jmx_password_456
monitor monitor_password_789
EOF
chown tomcat:tomcat /opt/tomcat/conf/jmxremote.*
chmod 600 /opt/tomcat/conf/jmxremote.password
chmod 644 /opt/tomcat/conf/jmxremote.access
# Step 4: Configure Tomcat startup with JMX
print_status "[4/8] Configuring Tomcat JMX startup options..."
cat > /opt/tomcat/bin/setenv.sh << EOF
#!/bin/bash
JAVA_OPTS="\$JAVA_OPTS -Dcom.sun.management.jmxremote"
JAVA_OPTS="\$JAVA_OPTS -Dcom.sun.management.jmxremote.port=9999"
JAVA_OPTS="\$JAVA_OPTS -Dcom.sun.management.jmxremote.rmi.port=9999"
JAVA_OPTS="\$JAVA_OPTS -Dcom.sun.management.jmxremote.ssl=false"
JAVA_OPTS="\$JAVA_OPTS -Dcom.sun.management.jmxremote.authenticate=true"
JAVA_OPTS="\$JAVA_OPTS -Dcom.sun.management.jmxremote.password.file=/opt/tomcat/conf/jmxremote.password"
JAVA_OPTS="\$JAVA_OPTS -Dcom.sun.management.jmxremote.access.file=/opt/tomcat/conf/jmxremote.access"
JAVA_OPTS="\$JAVA_OPTS -Djava.rmi.server.hostname=$HOSTNAME"
JAVA_OPTS="\$JAVA_OPTS -Xms512m -Xmx2048m"
export JAVA_OPTS
EOF
chmod 755 /opt/tomcat/bin/setenv.sh
chown tomcat:tomcat /opt/tomcat/bin/setenv.sh
# Step 5: Create systemd service
print_status "[5/8] Creating Tomcat systemd service..."
cat > /etc/systemd/system/tomcat.service << EOF
[Unit]
Description=Apache Tomcat 10 Web Application Container
Wants=network.target
After=network.target
[Service]
Type=forking
Environment=JAVA_HOME=$JAVA_HOME
Environment=CATALINA_PID=/opt/tomcat/temp/tomcat.pid
Environment=CATALINA_HOME=/opt/tomcat
Environment=CATALINA_BASE=/opt/tomcat
Environment='CATALINA_OPTS=-Xms512M -Xmx2048M -server -XX:+UseParallelGC'
Environment='JAVA_OPTS=-Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom'
ExecStart=/opt/tomcat/bin/startup.sh
ExecStop=/opt/tomcat/bin/shutdown.sh
User=tomcat
Group=tomcat
UMask=0007
RestartSec=10
Restart=always
[Install]
WantedBy=multi-user.target
EOF
chmod 644 /etc/systemd/system/tomcat.service
# Step 6: Install Prometheus JMX Exporter
print_status "[6/8] Installing Prometheus JMX Exporter..."
mkdir -p /opt/prometheus
wget -O /opt/prometheus/jmx_prometheus_javaagent-0.20.0.jar https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/0.20.0/jmx_prometheus_javaagent-0.20.0.jar
chown tomcat:tomcat /opt/prometheus/jmx_prometheus_javaagent-0.20.0.jar
chmod 644 /opt/prometheus/jmx_prometheus_javaagent-0.20.0.jar
cat > /opt/prometheus/tomcat-jmx-config.yml << 'EOF'
rules:
- pattern: 'Catalina<type=GlobalRequestProcessor, name=\"(\w+-\w+)-(\d+)\"><>(\w+):'
name: tomcat_$3_total
labels:
port: "$2"
protocol: "$1"
- pattern: 'Catalina<j2eeType=Servlet, WebModule=//([-a-zA-Z0-9+&@#/%?=~_|!:.,;]*[-a-zA-Z0-9+&@#/%=~_|]), name=([-a-zA-Z0-9+/$%~_-|!.]*), J2EEApplication=none, J2EEServer=none><>(requestCount|maxTime|processingTime|errorCount):'
name: tomcat_servlet_$3_total
labels:
module: "$1"
servlet: "$2"
EOF
chown tomcat:tomcat /opt/prometheus/tomcat-jmx-config.yml
chmod 644 /opt/prometheus/tomcat-jmx-config.yml
# Step 7: Configure firewall
print_status "[7/8] Configuring firewall..."
if command -v firewall-cmd &>/dev/null; then
firewall-cmd --permanent --add-port=8080/tcp --quiet || true
firewall-cmd --permanent --add-port=9999/tcp --quiet || true
firewall-cmd --permanent --add-port=8081/tcp --quiet || true
firewall-cmd --reload --quiet || true
elif command -v ufw &>/dev/null; then
ufw allow 8080/tcp --quiet || true
ufw allow 9999/tcp --quiet || true
ufw allow 8081/tcp --quiet || true
fi
# Step 8: Start and enable services
print_status "[8/8] Starting Tomcat service..."
systemctl daemon-reload
systemctl enable tomcat
systemctl start tomcat
# Verification
print_status "Verifying installation..."
sleep 5
if systemctl is-active tomcat --quiet; then
print_status "✓ Tomcat service is running"
else
print_error "✗ Tomcat service failed to start"
exit 1
fi
if netstat -tuln 2>/dev/null | grep -q ":8080" || ss -tuln 2>/dev/null | grep -q ":8080"; then
print_status "✓ Tomcat is listening on port 8080"
else
print_warning "! Tomcat may not be listening on port 8080 yet"
fi
if netstat -tuln 2>/dev/null | grep -q ":9999" || ss -tuln 2>/dev/null | grep -q ":9999"; then
print_status "✓ JMX is listening on port 9999"
else
print_warning "! JMX may not be listening on port 9999 yet"
fi
print_status "Installation completed successfully!"
echo
print_status "Next steps:"
echo "1. Access Tomcat Manager: http://$HOSTNAME:8080/manager"
echo "2. JMX is available on port 9999"
echo "3. Configure Prometheus to scrape JMX metrics on port 8081"
echo "4. JMX credentials: jmx-user/jmx_password_456, monitor/monitor_password_789"
echo "5. Add JMX agent to setenv.sh: -javaagent:/opt/prometheus/jmx_prometheus_javaagent-0.20.0.jar=8081:/opt/prometheus/tomcat-jmx-config.yml"
Review the script before running. Execute with: bash install.sh