← All Articles

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:

  1. Install IMTerm binary on new server
  2. Mount NFS share
  3. Create /etc/imterm/config.yaml with instance_id: node4
  4. Start IMTerm: systemctl start imterm
  5. Verify it's healthy: curl http://node4:8080/api/health
  6. Add to nginx upstream: server 10.0.0.24:8080 max_fails=3 fail_timeout=30s;
  7. 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