Multi-Server Deployment for 2000+ Seats
When to Go Multi-Server
A single IMTerm instance handles up to 500 concurrent sessions on typical server hardware (4 vCPUs, 8 GB RAM). This is sufficient for most deployments.
Go multi-server when you need:
- More than 500 concurrent users - horizontal scaling, each node adds ~500 sessions
- High availability - if one server fails, sessions route to the remaining nodes
- Zero-downtime upgrades - take one node offline, upgrade, bring it back, repeat
- Geographic distribution - nodes in different data centers, users connect to the nearest
You do NOT need multi-server for: - Small to medium organizations (under 300 concurrent users) - Dev/test environments - Organizations where brief maintenance windows are acceptable
Architecture
┌─────────────────┐
│ nginx │
│ (load balancer)│
│ port 443/80 │
└──────┬──────────┘
│ ip_hash sticky sessions
┌──────────────┼──────────────┐
│ │ │
┌────────┴───┐ ┌───────┴────┐ ┌─────┴──────┐
│ IMTerm │ │ IMTerm │ │ IMTerm │
│ Node 1 │ │ Node 2 │ │ Node 3 │
│ :8080 │ │ :8080 │ │ :8080 │
└─────┬──────┘ └──────┬─────┘ └────┬───────┘
│ │ │
└────────────────┴──────────────┘
│
┌───────────┴──────────┐
│ Shared NFS │
│ /var/lib/imterm/ │
│ - users.json │
│ - audit.jsonl │
│ - license.key │
│ - print archive │
│ - scripts/ │
└──────────────────────┘
TLS termination at nginx. Each IMTerm node runs HTTP on port 8080. All nodes share the same data directory over NFS.
Step-by-Step Setup
Step 1: Provision Servers
For a 2000-seat deployment: - 4 Linux servers (3 IMTerm nodes + 1 nginx/NFS server) - Minimum per IMTerm node: 4 vCPUs, 8 GB RAM, 40 GB disk - Minimum NFS server: 2 vCPUs, 4 GB RAM, 500 GB disk (for print archive) - Network: all servers on the same VLAN, 1 Gbps minimum
Recommended: separate the NFS server from nginx if budget allows. If not, nginx + NFS on the same server is workable.
Step 2: Set Up Shared NFS
On the NFS server:
# Install NFS server
dnf install -y nfs-utils # RHEL/Fedora
apt install -y nfs-kernel-server # Ubuntu
# Create shared directory
mkdir -p /var/lib/imterm
chown imterm:imterm /var/lib/imterm
# Export it
echo "/var/lib/imterm 10.0.0.0/24(rw,sync,no_subtree_check,no_root_squash)" >> /etc/exports
exportfs -ra
systemctl enable --now nfs-server
On each IMTerm node:
# Create local mount point
mkdir -p /var/lib/imterm
# Mount the NFS share (add to /etc/fstab for persistence)
echo "10.0.0.10:/var/lib/imterm /var/lib/imterm nfs defaults,_netdev,rsize=8192,wsize=8192 0 0" >> /etc/fstab
mount -a
# Verify
df -h /var/lib/imterm
Step 3: Install IMTerm on Each Node
Copy the binary to each node (or use the RPM/DEB package):
# From your build machine
scp bin/imterm node1:/usr/local/bin/imterm
scp bin/imterm node2:/usr/local/bin/imterm
scp bin/imterm node3:/usr/local/bin/imterm
# Make executable
chmod +x /usr/local/bin/imterm
# Create system user
useradd -r -s /sbin/nologin -d /var/lib/imterm imterm
Step 4: Create config.yaml on Each Node
The configuration is identical on all nodes except for instance_id. Place it at /etc/imterm/config.yaml:
Node 1 (instance_id: node1):
server:
addr: 0.0.0.0:8080
data_dir: /var/lib/imterm # NFS mount
instance_id: node1 # UNIQUE per node
log_level: info
license:
key_file: /var/lib/imterm/license.key # same key shared across nodes
Node 2 (instance_id: node2) and Node 3 (instance_id: node3) - identical except the instance_id.
Step 5: Configure nginx Upstream
On the nginx server, create /etc/nginx/conf.d/imterm.conf:
upstream imterm_nodes {
ip_hash; # sticky sessions by client IP
server 10.0.0.21:8080 max_fails=2 fail_timeout=30s;
server 10.0.0.22:8080 max_fails=2 fail_timeout=30s;
server 10.0.0.23:8080 max_fails=2 fail_timeout=30s;
}
server {
listen 443 ssl;
server_name imterm.corp.com;
ssl_certificate /etc/nginx/ssl/imterm.crt;
ssl_certificate_key /etc/nginx/ssl/imterm.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
location / {
proxy_pass http://imterm_nodes;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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_read_timeout 3600s;
proxy_send_timeout 3600s;
}
# Health check endpoint (no auth required)
location /api/health {
proxy_pass http://imterm_nodes;
access_log off;
}
}
server {
listen 80;
server_name imterm.corp.com;
return 301 https://$host$request_uri;
}
Why ip_hash? WebSocket sessions are stateful - a user connected to Node 1 must stay on Node 1. ip_hash hashes the client IP to consistently route to the same upstream. This works well in practice because corporate networks often have stable IP addresses per user.
Step 6: Configure nginx Health Checks
Add health checks so nginx stops routing to a failed node:
# In http {} block:
upstream imterm_nodes {
ip_hash;
server 10.0.0.21:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.22:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.23:8080 max_fails=3 fail_timeout=30s;
keepalive 32;
}
IMTerm responds to GET /api/health with HTTP 200 and JSON {"status":"ok","version":"2.0.1","instance":"node1"} when healthy. If a node returns 5xx or times out 3 times in 30 seconds, nginx marks it down and stops sending traffic to it.
Step 7: Create systemd Service on Each Node
/etc/systemd/system/imterm.service:
[Unit]
Description=IMTerm Terminal Server
After=network.target var-lib-imterm.mount
Requires=var-lib-imterm.mount
[Service]
Type=simple
User=imterm
Group=imterm
ExecStart=/usr/local/bin/imterm --config /etc/imterm/config.yaml
Restart=always
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
Note Requires=var-lib-imterm.mount - this ensures IMTerm does not start until the NFS share is mounted. The systemd mount unit name matches the mount path with / replaced by -.
Enable and start:
systemctl daemon-reload
systemctl enable --now imterm
Step 8: Verify the Cluster
From any node:
imterm stats --cluster
Output:
IMTerm Cluster Status
=====================
Node Status Sessions Memory Uptime
node1 ONLINE 142 387 MB 2d 14h
node2 ONLINE 156 401 MB 2d 14h
node3 ONLINE 98 321 MB 1d 6h
TOTAL 396 1.1 GB
Peak today: 412 sessions at 09:47
Licensed seats: 1500
From the nginx server, verify routing:
for i in 1 2 3; do
curl -s https://imterm.corp.com/api/health | python3 -m json.tool
done
You should see different instance values, confirming nginx is routing across nodes.
Cross-Instance Session Counting
IMTerm uses a heartbeat file mechanism for cross-instance coordination. Each node writes a file to the shared NFS directory:
/var/lib/imterm/cluster/node1.heartbeat
/var/lib/imterm/cluster/node2.heartbeat
/var/lib/imterm/cluster/node3.heartbeat
Each heartbeat file contains:
{"instance": "node1", "sessions": 142, "timestamp": "2026-06-27T14:32:00Z"}
Updated every 30 seconds. When imterm stats --cluster runs, it reads all heartbeat files and sums the session counts. Heartbeat files older than 2 minutes are treated as offline.
For larger deployments (10+ nodes): Enable Redis for coordination. Redis is faster for cross-instance queries and supports session affinity beyond IP:
redis:
enabled: true
addr: redis.corp.com:6379
password: "${REDIS_PASSWORD}"
db: 0
tls: true
Redis replaces the heartbeat file mechanism and provides: - Sub-second cross-instance session counts - Session resumption after node failure (experimental in v2.0) - Real-time cluster health dashboard
Shared Resources
All nodes read from and write to the NFS-mounted /var/lib/imterm/:
| Path | Contents | Access |
|---|---|---|
users.json |
User accounts and hashed passwords | Read/write by all nodes |
audit.jsonl |
Audit log (append-only) | Append by all nodes |
license.key |
License file | Read-only by all nodes |
printjobs/ |
Saved PDF print jobs | Read/write by all nodes |
archive/ |
Print archive (long-term) | Append/read by all nodes |
scripts/ |
Saved scripts | Read/write by all nodes |
config/ |
Connection profiles | Read/write by all nodes |
cluster/ |
Heartbeat files | Read/write by all nodes |
File locking: IMTerm uses advisory locks (flock) for users.json mutations. NFS fcntl locking is supported on modern NFS servers and Linux clients. This prevents corruption from concurrent writes during user management operations.
Audit log: The audit log is append-only. Each node holds the file open and writes lines independently. Since POSIX write to a file opened with O_APPEND is atomic up to PIPE_BUF (typically 4096 bytes), and audit log lines are short, concurrent writes do not corrupt the file.
Monitoring
Health Check Endpoint
GET /api/health
Response when healthy (HTTP 200):
{
"status": "ok",
"version": "2.0.1",
"instance": "node1",
"sessions": 142,
"uptime_sec": 215400
}
Response when unhealthy (HTTP 503):
{
"status": "degraded",
"reason": "NFS mount unavailable"
}
Integrate with Prometheus, Nagios, or your monitoring system by checking this endpoint.
Stats Dashboard
GET /api/v2/stats
Returns detailed per-node statistics including session count, memory usage, peak sessions, connections by protocol, and error rates. Requires admin authentication.
From the command line:
imterm usage --days 30
Outputs:
Usage Report, Last 30 Days
Peak concurrent: 412 (2026-06-15 09:47)
Average concurrent: 287
Total sessions started: 8,432
Protocols: TN5250=5,891, TN3270=1,203, VT220=1,338
Top users: jsmith(342), dbrown(289), acohen(201)...
Failover Scenario
When Node 2 goes down: 1. nginx marks Node 2 down after 3 failed health checks (90 seconds maximum) 2. Active sessions on Node 2 disconnect (users see "Connection lost" in the browser) 3. nginx routes new connections to Node 1 and Node 3 4. Users re-open IMTerm - they're routed to healthy nodes 5. Sessions resume (screen state is reconstructed from the host connection)
Session persistence: IMTerm does not checkpoint session state across the network. When a node fails, users must reconnect. Their terminal sessions to the AS/400/mainframe are also lost (the AS/400 sees a TCP disconnect). This is acceptable for most workloads - users sign back in and continue.
For zero-disconnect failover: Enable Redis session caching (experimental in v2.0). Redis stores screen snapshots every 5 seconds. When a node fails, the user reconnects to another node and their screen state is restored from Redis. AS/400 session continuity depends on the AS/400's connection timeout (typically 30-60 seconds).
Scaling Up: Adding a Node
To add Node 4 without disruption:
- Install IMTerm binary on new server
- Mount NFS share
- Create
/etc/imterm/config.yamlwithinstance_id: node4 - Start IMTerm:
systemctl start imterm - Verify it's healthy:
curl http://node4:8080/api/health - Add to nginx upstream:
server 10.0.0.24:8080 max_fails=3 fail_timeout=30s; - Reload nginx:
nginx -s reload
Traffic begins routing to Node 4 immediately. No downtime, no user impact.
Zero-Downtime Upgrade
Upgrade one node at a time:
# On Node 1: mark it temporarily offline in nginx by setting weight=0
# In nginx.conf, temporarily:
# server 10.0.0.21:8080 weight=0;
nginx -s reload
# Wait for existing connections to drain (5 minutes is usually enough)
sleep 300
# Upgrade Node 1
scp new-imterm node1:/usr/local/bin/imterm
ssh node1 "systemctl restart imterm"
ssh node1 "curl http://localhost:8080/api/health"
# Re-enable Node 1 in nginx
# Remove weight=0
nginx -s reload
# Repeat for Node 2 and Node 3
License Compliance at Scale
IMTerm's license is based on peak concurrent sessions, measured across all nodes.
When the peak exceeds your licensed seat count:
- IMTE9015W is logged to the audit log every hour
- An alert appears in the admin console
- New connections are still allowed (IMTerm does not hard-block connections when over license)
- You receive an email notification if alerts are configured
Review usage monthly:
imterm usage --days 30 --format json | python3 scripts/license-report.py
Contact sales@infomanta.com to increase your seat count.
Performance Benchmarks
Single-node performance (4 vCPU, 8 GB RAM): - 500 concurrent TN5250 sessions sustained - 17ms P50 WebSocket round-trip - 32 KB RAM per session - 1,620 session connects/second peak throughput - 10,000 API requests/second
Three-node cluster: - 1,500 concurrent sessions - Sub-20ms P50 (nginx add ~1ms) - Linear scaling: each node adds ~500 sessions
The bottleneck at high session counts is typically NFS I/O for audit logging. If audit log writes become slow, enable async audit logging:
audit:
async: true # buffer writes, flush every 5 seconds
See also: INTERNAL-MultiServerArchitecture.md, IMTerm Admin Guide section 11 (Scaling), nginx-cluster.conf