📑 Table of Contents
🔍 Overview
This guide provides step-by-step instructions for deploying MinusNow ITSM on a Linux on-premises server. The application is a Node.js-based IT Service Management platform with PostgreSQL database backend.
Architecture Diagram
┌─────────────────────────────────────────────────────────────────┐
│ Load Balancer / DNS │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Reverse Proxy (Nginx/Apache) │
│ Port 80/443 (SSL) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ MinusNow ITSM Application │
│ Node.js (Port 5000) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ PostgreSQL Database │
│ Port 5432 │
└─────────────────────────────────────────────────────────────────┘
💻 Server Requirements
Hardware Requirements
| Component | Minimum | Recommended | Enterprise |
|---|---|---|---|
| CPU | 8 vCPU (x86_64) | 16 vCPU (Xeon/EPYC) | 32+ vCPU (Xeon/EPYC) |
| RAM | 32 GB DDR4 | 64 GB DDR4 | 128+ GB DDR4/DDR5 |
| Storage | 500 GB NVMe SSD | 1 TB NVMe SSD | 2+ TB NVMe SSD RAID |
| Swap | 64 GB | 128 GB | 256 GB |
| Network | 1 Gbps | 10 Gbps | 25 Gbps+ |
Software Requirements
| Component | Version | Purpose |
|---|---|---|
| OS | Ubuntu 24.04 LTS / RHEL 9+ / Rocky Linux 9+ | Operating System |
| Node.js | 22.x LTS (Minimum 20.x LTS) | Application Runtime |
| PostgreSQL | 16+ (Minimum 15) | Database Server |
| Nginx | 1.24+ (Minimum 1.22) | Reverse Proxy |
| PM2 | 5.3+ Latest | Process Manager |
| Certbot | 2.x Latest | SSL Certificates |
| OpenSSL | 3.x+ | TLS/SSL Support |
Network Requirements
| Port | Protocol | Direction | Purpose |
|---|---|---|---|
| 22 | TCP | Inbound | SSH Access |
| 80 | TCP | Inbound | HTTP (Redirect to HTTPS) |
| 443 | TCP | Inbound | HTTPS |
| 5000 | TCP | Internal | Application |
| 5432 | TCP | Internal | PostgreSQL |
| 25/587 | TCP | Outbound | SMTP Email |
📋 Pre-Installation Checklist
Before proceeding, ensure you have:
- Root or sudo access to the server
- Static IP address assigned
- DNS A record pointing to server IP (e.g.,
itsm.yourdomain.com) - SMTP server credentials for email notifications
- SSL certificate (or use Let's Encrypt)
- Firewall rules configured
- Backup storage location identified
🔧 Server Preparation
1System Update
# Ubuntu/Debian
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget git unzip htop vim net-tools
# RHEL/CentOS
sudo dnf update -y
sudo dnf install -y curl wget git unzip htop vim net-tools
2Set Hostname and Timezone
# Set hostname
sudo hostnamectl set-hostname itsm-server
# Set timezone
sudo timedatectl set-timezone America/New_York # Adjust to your timezone
timedatectl
# Update /etc/hosts
echo "$(hostname -I | awk '{print $1}') itsm-server" | sudo tee -a /etc/hosts
3Configure Swap Space
# Check current swap
swapon --show
# Create swap file (4GB example)
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# Make permanent
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
# Optimize swap settings
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
echo 'vm.vfs_cache_pressure=50' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
4Configure Firewall
# Ubuntu (UFW)
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status
# RHEL/CentOS (firewalld)
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
sudo firewall-cmd --list-all
5Configure System Limits
# Edit limits.conf
sudo tee -a /etc/security/limits.conf << EOF
* soft nofile 65535
* hard nofile 65535
* soft nproc 65535
* hard nproc 65535
EOF
# Edit sysctl.conf for network optimization
sudo tee -a /etc/sysctl.conf << EOF
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_tw_reuse = 1
EOF
sudo sysctl -p
6Create Application User
# Create dedicated user for the application
sudo useradd -m -s /bin/bash minusnow
sudo usermod -aG sudo minusnow
# Create application directories
sudo mkdir -p /opt/minusnow-itsm
sudo mkdir -p /var/log/minusnow
sudo mkdir -p /var/lib/minusnow/data
sudo mkdir -p /var/lib/minusnow/backups
# Set ownership
sudo chown -R minusnow:minusnow /opt/minusnow-itsm
sudo chown -R minusnow:minusnow /var/log/minusnow
sudo chown -R minusnow:minusnow /var/lib/minusnow
🗄️ Database Installation
1Install PostgreSQL
# Ubuntu/Debian
sudo apt install -y postgresql postgresql-contrib
# RHEL/CentOS
sudo dnf install -y postgresql-server postgresql-contrib
sudo postgresql-setup --initdb
2Start PostgreSQL Service
sudo systemctl start postgresql
sudo systemctl enable postgresql
sudo systemctl status postgresql
3Create Database and User
# Switch to postgres user
sudo -u postgres psql
# In PostgreSQL shell, run:
CREATE USER minusnow WITH PASSWORD 'your_secure_password_here';
CREATE DATABASE minusnow_itsm OWNER minusnow;
GRANT ALL PRIVILEGES ON DATABASE minusnow_itsm TO minusnow;
# Enable required extensions
\c minusnow_itsm
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pg_trgm";
\q
4Configure PostgreSQL Authentication
# Edit pg_hba.conf
sudo vim /etc/postgresql/15/main/pg_hba.conf # Ubuntu
# or
sudo vim /var/lib/pgsql/data/pg_hba.conf # RHEL
# Add/modify these lines:
# TYPE DATABASE USER ADDRESS METHOD
local all minusnow md5
host all minusnow 127.0.0.1/32 md5
host all minusnow ::1/128 md5
5Configure PostgreSQL Performance
# Edit postgresql.conf - Recommended settings (adjust based on server RAM):
shared_buffers = 2GB # 25% of RAM
effective_cache_size = 6GB # 75% of RAM
maintenance_work_mem = 512MB
checkpoint_completion_target = 0.9
wal_buffers = 64MB
default_statistics_target = 100
random_page_cost = 1.1
effective_io_concurrency = 200
work_mem = 52428kB
min_wal_size = 1GB
max_wal_size = 4GB
max_worker_processes = 4
max_parallel_workers_per_gather = 2
max_parallel_workers = 4
6Restart PostgreSQL
sudo systemctl restart postgresql
sudo systemctl status postgresql
# Test connection
psql -U minusnow -d minusnow_itsm -h localhost -c "SELECT version();"
📦 Application Installation
1Install Node.js
# Install Node.js 20.x LTS
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
# Verify installation
node --version # Should show v20.x.x
npm --version
# Install PM2 globally
sudo npm install -g pm2
2Download Application
Option A: From Downloads Portal
# Download from portal
# https://www.minusnow.com/site/downloads
# Transfer to server
scp minusnow-itsm-artifact.zip user@server:/tmp/
# On server
cd /opt/minusnow-itsm
sudo -u minusnow unzip /tmp/minusnow-itsm-artifact.zip -d .
Option B: Installation Script
# Download and run installation script
curl -fsSL https://www.minusnow.com/site/download/install-linux.sh -o install.sh
chmod +x install.sh
sudo ./install.sh
Option C: From Source
# Clone repository
cd /opt/minusnow-itsm
sudo -u minusnow git clone https://github.com/minusnow/itsm.git .
# Install dependencies
sudo -u minusnow npm install
# Build application
sudo -u minusnow npm run build
3Configure Environment Variables
# Create environment file
sudo -u minusnow tee /opt/minusnow-itsm/.env << EOF
# Application
NODE_ENV=production
PORT=5000
HOST=0.0.0.0
# Database
DATABASE_URL=postgresql://minusnow:your_secure_password_here@localhost:5432/minusnow_itsm
# Session
SESSION_SECRET=$(openssl rand -hex 32)
# Email (SMTP)
SMTP_HOST=smtp.yourdomain.com
SMTP_PORT=587
SMTP_USER=noreply@yourdomain.com
SMTP_PASS=your_smtp_password
SMTP_FROM=MinusNow ITSM <noreply@yourdomain.com>
# Application URL
APP_URL=https://itsm.yourdomain.com
# File Storage
DATA_DIR=/var/lib/minusnow/data
BACKUP_DIR=/var/lib/minusnow/backups
# Logging
LOG_LEVEL=info
LOG_DIR=/var/log/minusnow
EOF
# Secure the file
sudo chmod 600 /opt/minusnow-itsm/.env
sudo chown minusnow:minusnow /opt/minusnow-itsm/.env
4Initialize Database Schema
cd /opt/minusnow-itsm
sudo -u minusnow npm run db:push
# Or run migrations
sudo -u minusnow npm run db:migrate
⚙️ Service Configuration
1Create PM2 Ecosystem File
sudo -u minusnow tee /opt/minusnow-itsm/ecosystem.config.js << EOF
module.exports = {
apps: [{
name: 'minusnow-itsm',
script: 'dist/index.js',
cwd: '/opt/minusnow-itsm',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 5000
},
env_file: '/opt/minusnow-itsm/.env',
error_file: '/var/log/minusnow/error.log',
out_file: '/var/log/minusnow/out.log',
log_file: '/var/log/minusnow/combined.log',
time: true,
max_memory_restart: '1G',
restart_delay: 5000,
max_restarts: 10,
autorestart: true,
watch: false
}]
};
EOF
2Start Application with PM2
cd /opt/minusnow-itsm
sudo -u minusnow pm2 start ecosystem.config.js
# Save PM2 configuration
sudo -u minusnow pm2 save
# Setup PM2 startup script
sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u minusnow --hp /home/minusnow
3Alternative: Systemd Service
sudo tee /etc/systemd/system/minusnow-itsm.service << EOF
[Unit]
Description=MinusNow ITSM Application
After=network.target postgresql.service
[Service]
Type=simple
User=minusnow
Group=minusnow
WorkingDirectory=/opt/minusnow-itsm
EnvironmentFile=/opt/minusnow-itsm/.env
ExecStart=/usr/bin/node dist/index.js
Restart=always
RestartSec=10
StandardOutput=append:/var/log/minusnow/out.log
StandardError=append:/var/log/minusnow/error.log
# Security
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/var/lib/minusnow /var/log/minusnow
[Install]
WantedBy=multi-user.target
EOF
# Enable and start service
sudo systemctl daemon-reload
sudo systemctl enable minusnow-itsm
sudo systemctl start minusnow-itsm
sudo systemctl status minusnow-itsm
🌐 Reverse Proxy Setup (Nginx)
# Install Nginx
sudo apt install -y nginx
# Create Nginx configuration
sudo tee /etc/nginx/sites-available/minusnow-itsm << 'EOF'
# Rate limiting
limit_req_zone $binary_remote_addr zone=minusnow_limit:10m rate=10r/s;
# Upstream
upstream minusnow_backend {
server 127.0.0.1:5000;
keepalive 32;
}
# HTTP - Redirect to HTTPS
server {
listen 80;
listen [::]:80;
server_name itsm.yourdomain.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
# HTTPS
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name itsm.yourdomain.com;
# SSL certificates (configure after certbot)
ssl_certificate /etc/letsencrypt/live/itsm.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/itsm.yourdomain.com/privkey.pem;
# SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_stapling on;
ssl_stapling_verify on;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Logging
access_log /var/log/nginx/minusnow-access.log;
error_log /var/log/nginx/minusnow-error.log;
# Client body size (for file uploads)
client_max_body_size 100M;
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
gzip_min_length 1000;
# Rate limiting
limit_req zone=minusnow_limit burst=20 nodelay;
# Main location
location / {
proxy_pass http://minusnow_backend;
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_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 300s;
proxy_connect_timeout 75s;
}
# Health check endpoint
location /health {
proxy_pass http://minusnow_backend;
access_log off;
}
}
EOF
# Enable site
sudo ln -sf /etc/nginx/sites-available/minusnow-itsm /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
# Test and reload Nginx
sudo nginx -t
sudo systemctl reload nginx
🔒 SSL/TLS Configuration
Using Let's Encrypt (Certbot)
# Install Certbot
sudo apt install -y certbot python3-certbot-nginx
# Obtain certificate
sudo certbot --nginx -d itsm.yourdomain.com
# Test automatic renewal
sudo certbot renew --dry-run
# Setup auto-renewal cron
echo "0 0,12 * * * root certbot renew --quiet" | sudo tee /etc/cron.d/certbot
Using Custom Certificate
# Copy certificates
sudo mkdir -p /etc/ssl/minusnow
sudo cp your-certificate.crt /etc/ssl/minusnow/server.crt
sudo cp your-private-key.key /etc/ssl/minusnow/server.key
sudo cp your-ca-bundle.crt /etc/ssl/minusnow/ca-bundle.crt
# Combine certificates
sudo cat /etc/ssl/minusnow/server.crt /etc/ssl/minusnow/ca-bundle.crt > /etc/ssl/minusnow/fullchain.crt
# Set permissions
sudo chmod 600 /etc/ssl/minusnow/*.key
sudo chmod 644 /etc/ssl/minusnow/*.crt
✅ Verification & Testing
Check Services Status
echo "=== PostgreSQL ==="
sudo systemctl status postgresql
echo "=== Application ==="
sudo -u minusnow pm2 status
echo "=== Nginx ==="
sudo systemctl status nginx
echo "=== Ports ==="
sudo netstat -tlnp | grep -E '(5000|5432|80|443)'
Test Application
# Test local access
curl -I http://localhost:5000/
curl -I http://localhost:5000/health
# Test through Nginx
curl -I https://itsm.yourdomain.com/
# Check application logs
sudo -u minusnow pm2 logs minusnow-itsm --lines 50
✅ Browser Testing Checklist:
- Open
https://itsm.yourdomain.comin browser - Verify SSL certificate (padlock icon)
- Test login functionality
- Test all major features
- Check browser console for errors
🎛️ Service Management
Using PM2
# Start
sudo -u minusnow pm2 start minusnow-itsm
# Stop
sudo -u minusnow pm2 stop minusnow-itsm
# Restart
sudo -u minusnow pm2 restart minusnow-itsm
# Reload (zero-downtime)
sudo -u minusnow pm2 reload minusnow-itsm
# View logs
sudo -u minusnow pm2 logs minusnow-itsm
# Monitor
sudo -u minusnow pm2 monit
# Status
sudo -u minusnow pm2 status
Using Systemd
# Start
sudo systemctl start minusnow-itsm
# Stop
sudo systemctl stop minusnow-itsm
# Restart
sudo systemctl restart minusnow-itsm
# Status
sudo systemctl status minusnow-itsm
# View logs
sudo journalctl -u minusnow-itsm -f
🔧 Troubleshooting
Application Won't Start
# Check logs
sudo -u minusnow pm2 logs minusnow-itsm --err
# Check environment file
sudo -u minusnow cat /opt/minusnow-itsm/.env
# Check Node.js version
node --version
# Rebuild dependencies
cd /opt/minusnow-itsm
sudo -u minusnow npm rebuild
Database Connection Failed
# Check PostgreSQL status
sudo systemctl status postgresql
# Check PostgreSQL logs
sudo tail -100 /var/log/postgresql/postgresql-15-main.log
# Test connection manually
psql -U minusnow -d minusnow_itsm -h localhost
# Restart PostgreSQL
sudo systemctl restart postgresql
Nginx 502 Bad Gateway
# Check if application is running
sudo -u minusnow pm2 status
# Check Nginx error logs
sudo tail -100 /var/log/nginx/minusnow-error.log
# Check upstream connection
curl -I http://localhost:5000/
# Restart Nginx
sudo systemctl restart nginx
SSL Certificate Issues
# Check certificate status
sudo certbot certificates
# Check certificate expiry
openssl s_client -connect itsm.yourdomain.com:443 2>/dev/null | openssl x509 -noout -dates
# Renew certificate
sudo certbot renew
Log Locations
| Log Type | Location |
|---|---|
| Application | /var/log/minusnow/combined.log |
| Application Errors | /var/log/minusnow/error.log |
| PM2 | ~/.pm2/logs/ |
| Nginx Access | /var/log/nginx/minusnow-access.log |
| Nginx Error | /var/log/nginx/minusnow-error.log |
| PostgreSQL | /var/log/postgresql/ |
| System | /var/log/syslog or journalctl |
💾 Backup & Recovery
Automated Backup Script
sudo -u minusnow tee /opt/minusnow-itsm/scripts/backup.sh << 'EOF'
#!/bin/bash
set -e
BACKUP_DIR="/var/lib/minusnow/backups"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=30
# Database backup
pg_dump -U minusnow -h localhost minusnow_itsm | gzip > "$BACKUP_DIR/db_$DATE.sql.gz"
# Application data backup
tar -czf "$BACKUP_DIR/data_$DATE.tar.gz" -C /var/lib/minusnow data/
# Configuration backup
tar -czf "$BACKUP_DIR/config_$DATE.tar.gz" \
/opt/minusnow-itsm/.env \
/opt/minusnow-itsm/ecosystem.config.js \
/etc/nginx/sites-available/minusnow-itsm
# Remove old backups
find "$BACKUP_DIR" -type f -mtime +$RETENTION_DAYS -delete
echo "Backup completed: $DATE"
EOF
chmod +x /opt/minusnow-itsm/scripts/backup.sh
# Setup daily cron
echo "0 2 * * * minusnow /opt/minusnow-itsm/scripts/backup.sh >> /var/log/minusnow/backup.log 2>&1" | sudo tee /etc/cron.d/minusnow-backup
Recovery Procedure
# Stop application
sudo -u minusnow pm2 stop minusnow-itsm
# Restore database
gunzip -c /var/lib/minusnow/backups/db_YYYYMMDD_HHMMSS.sql.gz | psql -U minusnow -h localhost minusnow_itsm
# Restore data
tar -xzf /var/lib/minusnow/backups/data_YYYYMMDD_HHMMSS.tar.gz -C /var/lib/minusnow
# Restart application
sudo -u minusnow pm2 start minusnow-itsm