Skip to content

App_servers

Application servers are runtime environments that execute application code and provide services like connection pooling, session management, and security. This chapter covers major application servers used in production: Tomcat and WildFly for Java, Gunicorn and uWSGI for Python, and PM2 for Node.js. Understanding application server configuration, deployment, and optimization is essential for DevOps and SRE engineers managing web applications.


┌─────────────────────────────────────────────────────────────────────────┐
│ TOMCAT ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Tomcat Server │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ Service (Catalina) │ │ │
│ │ ├─────────────────────────────────────────────────────────┤ │ │
│ │ │ │ │ │
│ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │
│ │ │ │ Engine │ │ Engine │ │ Engine │ │ │ │
│ │ │ │ (Catalina) │ │ (Catalina) │ │ (Catalina) │ │ │ │
│ │ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │
│ │ │ │ │ │ │ │ │
│ │ │ ▼ ▼ ▼ │ │ │
│ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │
│ │ │ │ Host │ │ Host │ │ Host │ │ │ │
│ │ │ │ (localhost) │ │ (example) │ │ (app1) │ │ │ │
│ │ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │
│ │ │ │ │ │ │ │ │
│ │ │ ▼ ▼ ▼ │ │ │
│ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │
│ │ │ │ Context │ │ Context │ │ Context │ │ │ │
│ │ │ │ (app1) │ │ (app2) │ │ (app3) │ │ │ │
│ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ Components: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Server - Top-level container, defines single JVM │ │
│ │ Service - Collection of Connectors + one Engine │ │
│ │ Engine - Processes requests, Catalina │ │
│ │ Host - Virtual host (domain-based) │ │
│ │ Context - Web application (WAR file or directory) │ │
│ │ Connector - Protocol handler (HTTP, AJP) │ │
│ │ Realm - User/role database │ │
│ │ Valve - Request processing component │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
<!-- ============================================================ -->
<!-- server.xml - Main Tomcat Configuration -->
<!-- ============================================================ -->
<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<!-- HTTP Connector -->
<Connector port="8080"
protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
maxThreads="200"
minSpareThreads="10"
acceptCount="100"
enableLookups="false"
URIEncoding="UTF-8"
compression="on"
compressionMinSize="2048"
noCompressionUserAgents="gozilla, traviata"
compressableMimeType="text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json"/>
<!-- AJP Connector (for Apache/mod_jk) -->
<!--
<Connector protocol="AJP/1.3"
address="::1"
port="8009"
redirectPort="8443"
maxThreads="200"/>
-->
<!-- Engine -->
<Engine name="Catalina" defaultHost="localhost">
<!-- Host for main application -->
<Host name="localhost"
appBase="webapps"
unpackWARs="true"
autoDeploy="true">
<!-- Access log valve -->
<Valve className="org.apache.catalina.valves.AccessLogValve"
directory="logs"
prefix="localhost_access_log"
suffix=".txt"
pattern="%h %l %u %t &quot;%r&quot; %s %b %D"/>
<!-- Error page configuration -->
<Valve className="org.apache.catalina.valves.ErrorReportValve"
showReport="false"
showServerInfo="false"/>
<!-- Context for specific app -->
<Context path="/api"
docBase="/var/lib/tomcat9/webapps/api"
reloadable="true"
cookies="true">
<Manager pathname="" />
</Context>
</Host>
<!-- Virtual host example -->
<!--
<Host name="app.example.com"
appBase="/var/www/apps"
unpackWARs="true"
autoDeploy="true">
<Alias>www.example.com</Alias>
<Valve className="org.apache.catalina.valves.AccessLogValve"
directory="/var/log/tomcat"
prefix="example_access_log"
suffix=".txt"/>
</Host>
-->
</Engine>
</Service>
</Server>
Terminal window
# ============================================================
# Tomcat Memory Configuration - setenv.sh
# ============================================================
# Create or edit /etc/tomcat9/setenv.sh (Debian/Ubuntu)
# or $CATALINA_HOME/bin/setenv.sh
#!/bin/bash
# Heap memory settings
# For a 4GB RAM server, allocate 2-3GB for Tomcat
CATALINA_OPTS="$CATALINA_OPTS -Xms2g"
CATALINA_OPTS="$CATALINA_OPTS -Xmx2g"
# Heap dump on OOM
CATALINA_OPTS="$CATALINA_OPTS -XX:+HeapDumpOnOutOfMemoryError"
CATALINA_OPTS="$CATALINA_OPTS -XX:HeapDumpPath=/var/log/tomcat/heapdump.hprof"
# G1GC (default in Java 11+, good for most workloads)
CATALINA_OPTS="$CATALINA_OPTS -XX:+UseG1GC"
CATALINA_OPTS="$CATALINA_OPTS -XX:MaxGCPauseMillis=200"
CATALINA_OPTS="$CATALINA_OPTS -XX:+ParallelRefProcEnabled"
# GC logging
CATALINA_OPTS="$CATALINA_OPTS -Xlog:gc*:file=/var/log/tomcat/gc.log:time,uptime,level,tags:filecount=10,filesize=100m"
# JMX for monitoring
CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote"
CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote.port=9010"
CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote.ssl=false"
CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote.authenticate=true"
# Application settings
CATALINA_OPTS="$CATALINA_OPTS -Djava.security.egd=file:/dev/./urandom"
CATALINA_OPTS="$CATALINA_OPTS -Dfile.encoding=UTF-8"
CATALINA_OPTS="$CATALINA_OPTS -Duser.timezone=UTC"
# PermGen/Metaspace (Java 8 and earlier)
# CATALINA_OPTS="$CATALINA_OPTS -XX:PermSize=256m"
# CATALINA_OPTS="$CATALINA_OPTS -XX:MaxPermSize=512m"
# For Java 9+
CATALINA_OPTS="$CATALINA_OPTS -XX:MetaspaceSize=256m"
CATALINA_OPTS="$CATALINA_OPTS -XX:MaxMetaspaceSize=512m"
export CATALINA_OPTS
Terminal window
# ============================================================
# Tomcat Deployment
# ============================================================
# Method 1: Deploy WAR file
cp myapp.war /var/lib/tomcat9/webapps/
# Tomcat auto-deploys (if autoDeploy=true)
# Method 2: Directory deployment
mkdir -p /var/lib/tomcat9/webapps/myapp
# Extract WAR or deploy exploded directory
# Configure context in META-INF/context.xml
# Method 3: Manager web interface
# Access: http://localhost:8080/manager/html
# Configure credentials in tomcat-users.xml
# Method 4: Deploy via script
curl -T myapp.war 'http://localhost:8080/manager/deploy?path=/myapp&update=true'
# Check deployment
ls -la /var/lib/tomcat9/webapps/
tail -f /var/log/tomcat9/catalina.out
# Undeploy
rm /var/lib/tomcat9/webapps/myapp.war
# Or: curl 'http://localhost:8080/manager/undeploy?path=/myapp'

# ============================================================
# gunicorn.conf.py - Production Configuration
# ============================================================
# Server socket
bind = "127.0.0.1:8000"
backlog = 2048
# Worker processes
workers = 4 # CPU cores * 2 + 1 (for CPU-bound)
worker_class = "sync" # sync, gevent, eventlet, tornado
workers_per_core = 2 # Workers per CPU core
# Worker settings
timeout = 120 # Request timeout in seconds
keepalive = 5 # Keep-alive connections
graceful_timeout = 30 # Time to finish requests on shutdown
max_requests = 1000 # Restart worker after N requests
max_requests_jitter = 50 # Random jitter for max_requests
# Logging
accesslog = "/var/log/gunicorn/access.log"
errorlog = "/var/log/gunicorn/error.log"
loglevel = "info"
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'
# Process naming
proc_name = "myapp"
# Server mechanics
daemon = False # Daemonize (use systemd instead)
pidfile = "/var/run/gunicorn.pid"
umask = 0
user = "www-data"
group = "www-data"
tmp_upload_dir = None
# SSL (or use nginx for SSL)
# keyfile = "/etc/ssl/private/server.key"
# certfile = "/etc/ssl/certs/server.crt"
# Preloading app (reduces memory usage with multiple workers)
preload_app = True
# Worker hooks
def on_starting(server):
"""Called just before the master process is initialized."""
pass
def on_reload(server):
"""Called to recycle workers during a reload via SIGHUP."""
pass
def when_ready(server):
"""Called just after the server is started."""
pass
def pre_fork(server, worker):
"""Called just before a worker is forked."""
pass
def post_fork(server, worker):
"""Called just after a worker has been forked."""
pass
def worker_int(worker):
"""Called just after a worker exited on SIGINT or SIGQUIT."""
pass
def worker_abort(worker):
"""Called when a worker received the SIGABRT signal."""
pass
Terminal window
# ============================================================
# Running Gunicorn
# ============================================================
# Basic usage
gunicorn -w 4 -b 127.0.0.1:8000 myapp:app
gunicorn -w 4 -b 127.0.0.1:8000 --config gunicorn.conf.py myapp:app
# With virtual environment
source venv/bin/activate
gunicorn -w 4 -b 127.0.0.1:8000 myapp:app
# Using systemd service
# /etc/systemd/system/myapp.service
[Unit]
Description=My Python Application
After=network.target
[Service]
Type=notify
User=www-data
Group=www-data
WorkingDirectory=/opt/myapp
Environment="PATH=/opt/myapp/venv/bin"
ExecStart=/opt/myapp/venv/bin/gunicorn -c /opt/myapp/gunicorn.conf.py myapp:app
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
# Start service
sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp

# ============================================================
# uwsgi.ini - Production Configuration
# ============================================================
[uwsgi]
# Application
module = myapp:app
master = true
# Socket
socket = /run/uwsgi/myapp.sock
chmod-socket = 660
vacuum = true
# Processes
processes = 4
threads = 2
cheaper = 2 # Dynamic scaling (overcommit)
cheaper-overload = 60 # Seconds before adding workers
cheaper-initial = 2 # Initial workers
cheaper-step = 1 # Workers to add at a time
# Timeouts
harakiri = 120 # Request timeout (kill slow requests)
harakiri-verbose = true
vacuum = true
die-on-term = true
# Buffering
buffer-size = 32768
# Logging
daemonize = /var/log/uwsgi/myapp.log
log-4xx = true
log-5xx = true
log-slow = true
slowlog = /var/log/uwsgi/slow.log
# Memory
reload-on-rss = 512 # Restart worker if RSS > 512MB
reload-on-as = 1024 # Restart worker if AS > 1024MB
# Persistence
py-autoreload = 0 # Disable in production
# Virtual environment
home = /opt/myapp/venv
# Python settings
pythonpath = /opt/myapp
python-autoreload = 0
wsgi-file = /opt/myapp/wsgi.py
callable = app
# Performance
thunder-lock = true # Prevent thundering herd
cheaper-algo = busyness # Scale based on busyness
# ============================================================
# Emperor Mode - Manage Multiple Apps
# ============================================================
[uwsgi]
# Emperor mode - monitor vassals directory
emperor = /etc/uwsgi/vassals
emperor-type = xml
# Stats
stats = /run/uwsgi/stats.sock
stats-http = true
# Logging
daemonize = /var/log/uwsgi/emperor.log
log-4xx = true
log-5xx = true
# Restart on configuration change
emperor-on-demand-dir = /run/uwsgi
touch-reload = /etc/uwsgi/vassals/%( vassal )
# ============================================================
# Individual app config in /etc/uwsgi/vassals/myapp.ini
# ============================================================
[uwsgi]
module = myapp:app
socket = /run/uwsgi/myapp.sock
chmod-socket = 660
processes = 4
threads = 2
die-on-term = true
vacuum = true

┌─────────────────────────────────────────────────────────────────────────┐
│ PM2 ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ PM2 Daemon │ │
│ │ ┌──────────────────────────────────────────────────────────┐ │ │
│ │ │ Process Manager │ │ │
│ │ ├──────────────────────────────────────────────────────────┤ │ │
│ │ │ - Process spawning │ │ │
│ │ │ - Health monitoring │ │ │
│ │ │ - Log management │ │ │
│ │ │ - Cluster management │ │ │
│ │ │ - Deployments │ │ │
│ │ │ - Auto-restart │ │ │
│ │ └──────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Application Instances │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Node.js │ │ Node.js │ │ Node.js │ │ Node.js │ │ │
│ │ │ App │ │ App │ │ App │ │ App │ │ │
│ │ │ #1 │ │ #2 │ │ #3 │ │ #4 │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ Features: │
│ - Load balancing (cluster mode) │
│ - Zero-downtime deployments │
│ - Log aggregation │
│ - Metrics and monitoring │
│ - Memory limit with auto-restart │
│ - Container integration (Docker/Kubernetes) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
// ============================================================
// ecosystem.config.js - PM2 Configuration
// ============================================================
module.exports = {
apps: [{
name: 'myapp',
script: './index.js',
// Instances: 0 = auto (CPU cores), or specific number
instances: 'max',
// Execution
exec_mode: 'cluster', // 'fork' or 'cluster'
cwd: '/var/www/myapp',
// Environment
env: {
NODE_ENV: 'development',
PORT: 3000
},
env_production: {
NODE_ENV: 'production',
PORT: 3000
},
// Arguments to script
args: '--inspect=0.0.0.0:9229',
// Scaling
instance_var: 'INSTANCE_ID',
// Memory limit - restart if exceeded
max_memory_restart: '1G',
// Restart policy
max_restarts: 10,
min_uptime: '10s',
restart_delay: 4000,
// Source map support
source_map_support: true,
// Log files
log_file: '/var/log/pm2/myapp.log',
out_file: '/var/log/pm2/out.log',
error_file: '/var/log/pm2/err.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
merge_logs: true,
// Monitoring
monitor: true,
// Graceful shutdown
kill_timeout: 5000,
wait_ready: true,
listen_timeout: 3000,
// Process identification
instance_name: 'myapp',
env_production: {
NODE_ENV: 'production'
}
}],
// Deploy configuration
deploy: {
production: {
user: 'deploy',
host: 'server.example.com',
ref: 'origin/main',
repo: 'git@github.com:user/repo.git',
path: '/var/www/production',
'post-deploy': 'npm install && pm2 reload ecosystem.config.js --env production'
}
}
}
Terminal window
# ============================================================
# PM2 Common Commands
# ============================================================
# Start application
pm2 start ecosystem.config.js
pm2 start index.js -i 4 # 4 instances
pm2 start index.js --name myapp # Named
# Process management
pm2 list # List processes
pm2 status
pm2 info myapp # Detailed info
pm2 monit # Real-time monitoring
pm2 logs myapp # View logs
pm2 logs myapp --err # Error logs only
# Process control
pm2 stop myapp # Stop
pm2 restart myapp # Restart
pm2 reload myapp # Zero-downtime reload
pm2 delete myapp # Remove
# Scaling
pm2 scale myapp +3 # Add 3 instances
pm2 scale myapp 8 # Scale to 8 instances
# Save/restore
pm2 save # Save current process list
pm2 resurrect # Restore saved processes
# Startup scripts
pm2 startup # Generate startup script
pm2 startup ubuntu # For specific init system
# Cluster mode (load balancing)
pm2 start index.js -i 4 # Start 4 instances
pm2 reload myapp # Zero-downtime reload
# Performance monitoring
pm2 perf monitor # Performance metrics
# Keymetrics integration
pm2 link <secret> <public> # Connect to keymetrics.io

Terminal window
# ============================================================
# Application Server Security Checklist
# ============================================================
# 1. Run with minimal privileges
# - Use dedicated user/group
useradd -r -s /sbin/nologin tomcat
chown -R tomcat:tomcat /opt/tomcat
# 2. Disable default accounts
# - Change default passwords
# - Remove admin/manager interfaces if not needed
# 3. Use SSL/TLS
# - Configure HTTPS only
# - Use strong ciphers
# 4. Rate limiting
# - Configure connection limits
# - Implement request throttling
# 5. Headers
# - X-Frame-Options: DENY
# - X-Content-Type-Options: nosniff
# - X-XSS-Protection
# - Content-Security-Policy
# 6. Session security
# - Secure cookies
# - HttpOnly flag
# - Session timeout
# 7. File upload restrictions
# - Limit file types
# - Scan uploads for malware
# - Store outside web root
Terminal window
# ============================================================
# Application Server Performance Checklist
# ============================================================
# 1. Connection pooling
# - Configure database connection pool
# - Appropriate pool size
# 2. Caching
# - Enable application-level caching
# - Use Redis/Memcached
# - Configure HTTP caching
# 3. Compression
# - Enable gzip compression
# - Compress static assets
# 4. Static files
# - Serve via CDN or separate server
# - Use appropriate headers
# 5. Monitoring
# - Application Performance Monitoring (APM)
# - Custom metrics
# - Health check endpoints
# 6. Resource limits
# - Configure memory limits
# - Set request timeouts
# - Limit concurrent connections

┌─────────────────────────────────────────────────────────────────────────┐
│ APPLICATION SERVER INTERVIEW QUESTIONS │
├─────────────────────────────────────────────────────────────────────────┤
Q1: What is the difference between Gunicorn and uWSGI? │
A1: │
| Feature | Gunicorn | uWSGI | |
|----------------|----------------------|----------------------------| |
| Language | Python only | Multi-language (C) | |
| Configuration | Python/command | INI/command | |
| Emperor mode | No | Yes (multiple apps) | |
| Performance | Good | Very good | |
| Features | Simpler | More features | |
| Community | Large | Large | |
| Workers | Pre-fork only | Pre-fork + threads | |
Both are production-ready WSGI servers. uWSGI offers more options │
for complex deployments. Gunicorn is simpler and integrates well │
with Django. │
─────────────────────────────────────────────────────────────────────────┤
Q2: How do you configure Tomcat memory settings? │
A2: │
Set in setenv.sh or CATALINA_OPTS: │
- -Xms: Initial heap size (e.g., -Xms2g) │
- -Xmx: Maximum heap size (e.g., -Xmx2g) │
- -XX:+UseG1GC: G1 garbage collector │
- -XX:MaxGCPauseMillis: Target pause time │
- -XX:+HeapDumpOnOutOfMemoryError: For debugging │
- -XX:HeapDumpPath: Where to save heap dump │
- For Java 8: -XX:PermSize, -XX:MaxPermSize │
- For Java 9+: -XX:MetaspaceSize, -XX:MaxMetaspaceSize │
General rule: Set Xms=Xmx for consistent memory, allocate 70-80% │
of available RAM to the heap. │
─────────────────────────────────────────────────────────────────────────┤
Q3: What is PM2 and why would you use it? │
A3: │
PM2 is a Node.js process manager: │
- Process clustering and load balancing │
- Zero-downtime deployments │
- Automatic restarts on crashes │
- Log management and aggregation │
- Memory/CPU monitoring │
- Cluster mode for parallel processing │
- Container integration │
- Useful for production Node.js applications │
─────────────────────────────────────────────────────────────────────────┤
Q4: How do you deploy a Java web application to Tomcat? │
A4: │
1. Build WAR file: mvn package or gradle build │
2. Deploy: │
a. Copy WAR to webapps/ (auto-deploy) │
b. Use Manager web interface │
c. Deploy via curl/Ant script │
3. Configure context in server.xml or context.xml │
4. Set up database connections in context.xml or JNDI │
5. Configure logging │
6. Test in staging first │
7. Monitor logs for startup errors │
─────────────────────────────────────────────────────────────────────────┤
Q5: What is the difference between sync and async workers in Gunicorn?│
A5: │
- sync: One request per worker at a time (blocking) │
- gevent/eventlet: Async using greenlets (non-blocking I/O) │
- tornado: Async HTTP client/server │
- gthread: Threads within sync workers │
For I/O-bound apps, async workers can handle more connections. │
For CPU-bound apps, sync workers are better. │
─────────────────────────────────────────────────────────────────────────┤
Q6: How do you configure connection pooling for Java applications? │
A6: │
Tomcat provides JNDI DataSource: │
<Resource name="jdbc/mydb" │
auth="Container" │
type="javax.sql.DataSource" │
maxTotal="100" │
maxIdle="30" │
maxWaitMillis="10000" │
username="user" │
password="pass" │
driverClassName="org.postgresql.Driver"│
url="jdbc:postgresql://localhost/mydb"/> │
Or use HikariCP in the application for better performance. │
─────────────────────────────────────────────────────────────────────────┤
Q7: How do you implement zero-downtime deployments? │
A7: │
1. Load balancer: Remove old server from pool │
2. Application server: │
- Gunicorn: gunicorn -HUP (graceful reload) │
- PM2: pm2 reload │
- Tomcat: Deploy new version, don't stop old │
3. Health check: Verify new version is ready │
4. Add back to pool │
5. Remove old server │
Tools: Kubernetes rolling updates, Ansible, Capistrano │
─────────────────────────────────────────────────────────────────────────┤
Q8: What is the difference between fork and cluster mode in PM2? │
A8: │
- fork mode (default): │
- Single process, single instance │
- Good for microservices │
- Manual scaling │
- cluster mode: │
- Node.js cluster module for load balancing │
- Multiple instances (CPUs) │
- Zero-downtime reload │
- Shared port │
- Better for high-traffic apps │
─────────────────────────────────────────────────────────────────────────┤
Q9: How do you monitor application server health? │
A9: │
- JVM: JConsole, VisualVM, Java Mission Control │
- Metrics: Prometheus + Grafana │
- APM: New Relic, Datadog, Elastic APM │
- Logs: ELK stack │
- Health endpoints: /health, /metrics │
- Custom metrics in application │
- PM2: pm2 monit │
- Tomcat: JMX + manager app │
─────────────────────────────────────────────────────────────────────────┤
Q10: What is the purpose of a reverse proxy in front of application │
servers? │
A10: │
- SSL termination │
- Load balancing │
- Static file serving │
- Caching │
- Compression │
- Request filtering │
- IP anonymization │
- DDoS protection │
- Common: Nginx, HAProxy, Apache │
└─────────────────────────────────────────────────────────────────────────┘

Terminal window
# Tomcat
catalina.sh start # Start
catalina.sh stop # Stop
# Set memory: $CATALINA_HOME/bin/setenv.sh
# Gunicorn
gunicorn -w 4 -b 127.0.0.1:8000 app:app
# Config: gunicorn.conf.py
# uWSGI
uwsgi --ini uwsgi.ini
# Emperor: uwsgi --emperor /etc/uwsgi/vassals
# PM2
pm2 start ecosystem.config.js
pm2 list
pm2 logs
pm2 restart myapp

  • Tomcat: Java application server, configure memory in setenv.sh
  • Gunicorn: Python WSGI server, workers = CPU × 2 + 1
  • uWSGI: Python with more features, Emperor mode for multi-app
  • PM2: Node.js process manager, cluster mode for scaling
  • Security: Run with minimal privileges, use SSL, configure headers
  • Performance: Connection pooling, caching, compression

Chapter 71: Web Servers Summary


Last Updated: February 2026