Project Oxygen & Ideo-LabIDEO LAB Dashboard 2026

Apache Tomcat — Guide hyper complet (Production / DevOps)

Tomcat est un Servlet Container (serveur web applicatif Java) : léger, performant, omniprésent (Spring / APIs REST), et très utilisé en production derrière un reverse-proxy. Ce guide est conçu pour être opérationnel : architecture interne, fichiers de conf, déploiement WAR, sécurité, performance, logs/monitoring, Docker/Kubernetes, troubleshooting, et checklists de prod.

Servlet/JSP Reverse proxy Hardening Tuning Logs/Monitoring Docker/K8s
1

Panorama & concepts

Tomcat vs WebLogic/JBoss, modes de déploiement, composants internes, vocabulaire essentiel.

PositionnementServletWAR
2

Architecture interne

Catalina / Coyote / Jasper, pipeline de requêtes, Engine/Host/Context, valves/filters.

CatalinaConnectorsValves
3

Install & layout

Installation Linux, arborescence, variables, systemd, upgrades propres, multi-instances.

LinuxsystemdUpgrade
4

Configuration core

server.xml, context.xml, conf/Catalina, resources JNDI, UTF-8, compression, headers.

server.xmlJNDIHTTP
5

Déploiement & CI/CD

WAR, manager (à éviter), rolling/blue-green, Jenkins, artefacts, stratégies de rollback.

CI/CDRollbackBlue/Green
6

Sécurité & hardening

HTTPS/TLS, suppression apps default, manager/host-manager, headers, secrets, permissions.

TLSHardeningHeaders
7

Performance & tuning

Threads, pools, keep-alive, NIO/NIO2, compression, caches, JVM/GC, sizing & tests.

ThreadsJVMGC
8

Reverse proxy

Nginx/Apache, AJP (prudence), sticky sessions, headers X-Forwarded, websockets.

NginxAJPX-Forwarded
9

HA & clustering

Sessions (replication vs sticky), stateless, cache externalisé, multi-nodes, limites.

HASessionsStateless
10

Logs & observabilité

Access logs, JULI, logrotate, JMX, Prometheus, Grafana, métriques essentielles.

JMXPrometheusGrafana
11

Docker & Kubernetes

Images, multi-stage, conf externalisée, probes, resources, Helm patterns, secrets.

DockerK8sHelm
12

Troubleshooting

Erreurs fréquentes, OOM, threads bloqués, timeouts, TLS, 502/504, checklist prod.

DebugOOMChecklist

1) Panorama & concepts — où se place Tomcat ?

Tomcat = Servlet Container / Web Application Server

Tomcat exécute des Servlets, JSP, WebSockets, et sert de runtime web Java pour des applis (souvent Spring). Il est léger et très stable, mais n’embarque pas nativement toute la pile Java EE / Jakarta EE (EJB/JTA/JMS enterprise intégrés) comme WebLogic/WebSphere.

Règle simple : si ton application est “web Java” (Spring MVC/Boot, REST), Tomcat est souvent suffisant. Si tu as un SI Java EE lourd (transactions distribuées, EJB, services d’intégration historiques), on parle plutôt d’App Server.
  • Déploiement : .war (classique) ou .jar (Spring Boot embedded, sans Tomcat externe).
  • Topologie : standalone / derrière Nginx / multi-nœuds + load balancer.
  • Patterns : stateless + cache externe (Redis) = HA “propre”.
Tomcat vs WebLogic/JBoss (très résumé)
  • Tomcat : web runtime (Servlet/JSP), simple, rapide, coût = 0, très populaire.
  • WebLogic/WebSphere : app servers enterprise, features intégrées (JTA/EJB/JMS etc.), plus lourds, souvent licenciés.
  • JBoss/WildFly : app server open-source enterprise, mais “plus lourd” qu’un Tomcat.
Anti-pattern fréquent : tenter d’utiliser Tomcat comme un “mini WebLogic” en empilant des modules externes. Ça fonctionne parfois, mais devient vite fragile (ops, sécurité, upgrades).
Vocabulaire essentiel
  • Connector : endpoint réseau (HTTP/HTTPS/AJP) géré par Coyote.
  • Engine : “moteur” servlet global. Contient des Host.
  • Host : hôte virtuel (nom de domaine). Contient des Context.
  • Context : application web (un WAR déployé) + paramètres.
  • Valve : composant de pipeline (logs, contrôle, réécriture, sécurité).
  • Realm : auth (JDBC/LDAP/JAAS) côté Tomcat.
  • JNDI Resource : datasource pool DB exposé à l’application.
“Schéma mental” d’une requête
Client -> Reverse Proxy (optionnel) -> Tomcat Connector -> Engine -> Host -> Context -> Filters -> Servlet -> Response

Les points d’optimisation/contrôle : connector (threads, timeouts), valves (logs), app (pool DB), JVM (GC).

À retenir : 80% des problèmes de prod Tomcat sont : mauvais timeouts, threads saturés, GC/OOM, pool DB, ou reverse-proxy mal configuré.

2) Architecture interne — Catalina / Coyote / Jasper

Composants
  • Coyote : layer réseau (HTTP/1.1, HTTP/2 selon config, NIO/NIO2).
  • Catalina : container Servlet (Engine/Host/Context, pipeline, sessions).
  • Jasper : compilation/exécution JSP (souvent à éviter en prod si possible).
  • JULI : système de logs Tomcat (java.util.logging optimisé).
  • JMX : MBeans pour monitoring / introspection.
Conseil prod : pré-compiler / minimiser l’usage de JSP si tu peux. Les warmups + compilations dynamiques peuvent créer des pics CPU/IO au démarrage.
Pipeline & points d’extension
  • Filters (app) : auth, CORS, gzip, tracing… au niveau application.
  • Valves (container) : accès logs, restrictions IP, remoteip, etc.
  • Listeners : hooks au démarrage/arrêt, lifecycle.
<Host ...>
  <Valve className="org.apache.catalina.valves.AccessLogValve" ... />
  <Valve className="org.apache.catalina.valves.RemoteIpValve" ... />
</Host>

Valves = excellent endroit pour standardiser les logs et la “réalité” des IP client derrière proxy.

3) Installation & layout — Linux, systemd, upgrades propres

Arborescence (rappel)
bin/      start/stop scripts
conf/     server.xml, context.xml, web.xml, logging.properties
lib/      libs partagées (attention !)
webapps/  WARs déployés (auto-deploy possible)
logs/     catalina.out + logs JULI
temp/     fichiers temporaires
work/     compilation JSP, caches
Pattern propre : séparer CATALINA_BASE (instance) et CATALINA_HOME (binaire). Ça facilite les upgrades et les multi-instances.
systemd (exemple)
[Unit]
Description=Tomcat
After=network.target

[Service]
Type=forking
User=tomcat
Group=tomcat
Environment="JAVA_HOME=/usr/lib/jvm/java-17"
Environment="CATALINA_HOME=/opt/tomcat"
Environment="CATALINA_BASE=/srv/tomcat-instance01"
Environment="CATALINA_OPTS=-Xms2g -Xmx2g -XX:+UseG1GC"
ExecStart=/opt/tomcat/bin/startup.sh
ExecStop=/opt/tomcat/bin/shutdown.sh
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Adapte les paths/permissions. En prod, externalise les logs et pense aux ulimits (nofile).

4) Configuration core — server.xml, context.xml, JNDI, HTTP

Connector HTTP (NIO) — base solide
<Connector port="8080"
           protocol="org.apache.coyote.http11.Http11NioProtocol"
           maxThreads="300"
           acceptCount="100"
           connectionTimeout="20000"
           maxKeepAliveRequests="200"
           keepAliveTimeout="15000"
           compression="on"
           compressibleMimeType="text/html,text/xml,text/plain,text/css,application/json,application/javascript"
           URIEncoding="UTF-8" />
Astuce : le sizing threads/keepalive dépend fortement du reverse proxy et du profil de charge (latence, payload, DB).
Datasource JNDI (pool) — exemple robuste
<Resource name="jdbc/app"
          auth="Container"
          type="javax.sql.DataSource"
          factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
          driverClassName="org.postgresql.Driver"
          url="jdbc:postgresql://db:5432/app"
          username="app"
          password="CHANGE_ME"
          maxActive="60"
          maxIdle="20"
          minIdle="10"
          maxWait="10000"
          testOnBorrow="true"
          validationQuery="SELECT 1"
          validationInterval="30000"
          removeAbandoned="true"
          removeAbandonedTimeout="60"
          logAbandoned="true" />
Très important : un pool DB mal réglé peut saturer la DB, créer des timeouts, et “faire croire” que Tomcat est lent.
UTF-8 & compression
  • URIEncoding en UTF-8 sur le connector.
  • Côté app : s’assurer que les filtres encodage (ou Spring) forcent UTF-8.
  • Compression : utile pour JSON/HTML, à benchmarker (CPU vs bande passante).
# Exemple côté app (Spring Boot)
server.servlet.encoding.charset=UTF-8
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true
Timeouts : l’arme anti “prod qui freeze”
  • connectionTimeout : handshake initial.
  • keepAliveTimeout : connexions persistantes.
  • proxy_read_timeout côté Nginx : doit être cohérent avec ton app.
  • DB timeouts : au niveau driver/pool (maxWait).
Symptôme classique : 502/504 au proxy + threads Tomcat saturés + DB lente = timeouts incohérents. Il faut harmoniser proxy, Tomcat, app, DB.
Auto-deploy : prudence en prod

Tomcat peut auto-déployer depuis webapps/ (war/dir). En prod, préfère des déploiements explicites (CI/CD) et désactive l’auto-reload si tu veux de la stabilité.

<Host ... autoDeploy="false" deployOnStartup="false"> ... </Host>

Pour du rolling/blue-green, pilote le LB/proxy plutôt que le hot-deploy.

5) Déploiement & CI/CD — WAR, rollback, blue/green

Stratégies de déploiement (classiques)
  • WAR atomique : publier un .war versionné (artifact repository), déployer via script.
  • Rolling : 2+ instances derrière LB, mise à jour nœud par nœud.
  • Blue/Green : deux pools complets, switch LB (rollback instantané).
  • Canary : trafic partiel sur nouvelle version (observabilité forte).
Best practice : une release = un artifact immuable + config externalisée (env vars / files) + possibilité de rollback en 1 commande.
Jenkins (pseudo pipeline)
stage('Build') { sh 'mvn -DskipTests clean package' }
stage('Publish') { sh 'curl -T target/app.war $NEXUS_URL' }
stage('Deploy') {
  sh '''
    ssh tomcat01 "deploy_war.sh app.war"
    ssh tomcat02 "deploy_war.sh app.war"
  '''
}
stage('Smoke') { sh 'curl -f https://app/health' }

Le “deploy_war.sh” gère backup, stop/start, healthcheck, rollback si KO.

6) Sécurité & hardening — TLS, apps par défaut, headers

Checklist hardening (indispensable)
  • Supprimer docs, examples, host-manager, manager si inutile.
  • Si manager requis : IP allowlist + auth forte + pas exposé Internet.
  • Mettre Tomcat derrière Nginx/Apache (WAF/rate-limit/headers/TLS centralisé).
  • Mettre des permissions strictes sur conf/ et secrets (pas dans le WAR).
  • Mettre à jour Tomcat/Java régulièrement (CVE).
Erreur fatale : exposer /manager sur Internet. C’est l’une des causes historiques de compromissions.
Headers sécurité (proxy ou app)
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer" always;
add_header Content-Security-Policy "default-src 'self'" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

Adapter CSP selon tes besoins (scripts externes, CDN, etc.).

7) Performance & tuning — threads, keepalive, JVM/GC

Tuning Tomcat (pragmatique)
  • maxThreads : nombre de requêtes simultanées par connector.
  • acceptCount : backlog en attente si threads saturés.
  • keepAlive : réduit le coût TCP/TLS mais peut retenir des sockets.
  • compression : réduit bandwidth mais augmente CPU.
  • pool DB : dimensionner selon DB + latence + transactions.
Diagnostic rapide : si 95p latency grimpe -> regarder (1) DB (2) threads busy (3) GC (4) reverse proxy timeouts.
JVM flags (base saine Java 17)
-Xms4g
-Xmx4g
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/tomcat/heapdumps
-Djava.security.egd=file:/dev/urandom

Toujours valider par tests de charge + monitoring GC (pas au feeling).

Piège : augmenter maxThreads sans augmenter la capacité DB/cache peut empirer la situation (effet “thundering herd”).

8) Reverse proxy — Nginx/Apache, X-Forwarded, WebSockets

Nginx (exemple)
location / {
  proxy_pass http://tomcat_upstream;
  proxy_set_header Host $host;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto $scheme;
  proxy_read_timeout 60s;
}

Pour WebSockets : ajouter Upgrade/Connection headers.

RemoteIpValve (Tomcat) — IP client réelle
<Valve className="org.apache.catalina.valves.RemoteIpValve"
       internalProxies="10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+" 
       remoteIpHeader="x-forwarded-for"
       proxiesHeader="x-forwarded-by"
       protocolHeader="x-forwarded-proto" />
Indispensable : sinon tes logs/app verront l’IP du proxy, pas celle du client (et ta sécurité/analytics seront faussées).
AJP : si tu l’utilises, sécurise-le strictement (secret/ports non exposés). Beaucoup d’incidents historiques viennent d’AJP mal protégé.

9) HA & clustering — sessions, stateless, limites

Approches HA (du plus propre au plus “legacy”)
  • Stateless + JWT/OAuth2 + cache externalisé (Redis) => HA simple, scalable.
  • Sticky sessions au LB (affinité) => simple mais failover limité.
  • Session replication Tomcat => possible, mais coûteux/complexe (latence/serialization).
Conseil : si possible, vise stateless. La réplication de session est souvent un “dernier recours”.
Notes techniques (sessions)
  • La session doit être sérialisable (objets Java).
  • Risque : bloat mémoire, GC, latence réseau.
  • Sur K8s : préférer externaliser l’état plutôt que de compter sur sticky.
# Pattern recommandé
App stateless
Redis (session/cache)
DB (source of truth)
LB (round-robin)

10) Logs & observabilité — AccessLog, JMX, Prometheus, Grafana

Access logs (Valve)
<Valve className="org.apache.catalina.valves.AccessLogValve"
       directory="logs"
       prefix="access_log"
       suffix=".log"
       pattern="%h %l %u %t "%r" %s %b %D"
       rotatable="true" />

%D = durée microsecondes (très utile). Coupler avec RemoteIpValve derrière proxy.

JMX / métriques clés
  • Threads : currentThreadCount, currentThreadsBusy
  • Connexions : keepalive, erreurs, timeouts
  • JVM : heap used, GC pauses, OOM
  • App : pool DB, latences endpoints, taux d’erreur
Grafana “minimum vital” : RPS, p95 latency, 5xx rate, threads busy, heap used, GC pause, pool DB active/wait.

11) Docker & Kubernetes — images, conf externalisée, probes, Helm

Dockerfile (simple)
FROM tomcat:9-jdk17
# enlever les apps par défaut
RUN rm -rf /usr/local/tomcat/webapps/*
COPY target/app.war /usr/local/tomcat/webapps/ROOT.war
# conf externalisée possible via volume / env
EXPOSE 8080
Important : externaliser la config (DB creds, endpoints). Ne bake pas de secrets dans l’image.
Kubernetes (pattern)
  • Readiness : endpoint /health (app) ou TCP check.
  • Liveness : évite les false positives (GC long). Préférer une probe moins agressive.
  • Resources : requests/limits cohérents avec heap JVM (ne pas OOM-kill).
  • Config : ConfigMaps/Secrets + volumes.
  • Helm : values.yaml (env, probes, resources, ingress, autoscaling).
# Règle pratique : heap Xmx <= ~70% du memory limit
# ex: limit 2Gi => Xmx ~1.3Gi (à valider)

12) Troubleshooting — erreurs fréquentes + checklist prod

Symptômes -> causes probables
  • 502/504 : timeouts proxy <-> tomcat <-> app/DB incohérents.
  • Latence qui explose : DB lente, GC, threads saturés, locks.
  • OOM : heap trop petit, fuite mémoire, cache session, trop d’objets en session.
  • CPU 100% : boucle app, JSP compile, GC thrash, regex logs, compression trop agressive.
  • Threads busy : endpoint lent + pool DB saturé + acceptCount qui gonfle.
Doit être visible en dashboard : threads busy + pool DB active + p95 latency + 5xx. Si tu n’as pas ça, tu “pilotes à l’aveugle”.
Checklist production (rapide)
  • Apps par défaut supprimées / manager non exposé.
  • TLS géré au proxy + HSTS + headers de sécurité.
  • RemoteIpValve + access logs avec durée.
  • JVM flags + heapdump OOM + logs GC.
  • Logrotate / externalisation logs.
  • Monitoring JMX + alerting (threads busy, heap, 5xx).
  • CI/CD immuable + rollback.
  • Config externalisée + secrets.
Objectif : une prod Tomcat “ennuyeuse” = stable, observable, patchée, et simple à rollback.