Why Server Requirements Matter
Choosing the wrong server configuration for your tech stack is one of the most expensive mistakes you can make. Over-provision and you waste money every month. Under-provision and your application crashes under load. This guide gives you concrete, numbers-backed guidance for sizing servers across every major tech stack — from Node.js and Python to Java and Go — including database tuning, caching, cloud provider comparisons, and real-world cost analysis.
Node.js Server Requirements
Understanding Node.js Performance Characteristics
Node.js is single-threaded but event-driven with non-blocking I/O. This means a single Node.js process can handle thousands of concurrent connections, but CPU-intensive tasks block the event loop. The key is to use multiple processes via PM2 cluster mode.
Recommended Specs
| Traffic Level | CPU | RAM | Storage | Concurrent Users |
| Small (blog, portfolio) | 1 vCPU | 1 GB | 25 GB SSD | ~500 |
| Medium (SaaS, API) | 2-4 vCPU | 4-8 GB | 50 GB SSD | ~5,000 |
| Large (high-traffic app) | 8+ vCPU | 16+ GB | 100+ GB SSD | ~50,000+ |
PM2 Production Setup
# Install PM2 globally
npm install -g pm2
# Start with cluster mode (uses all CPU cores)
pm2 start server.js -i max --name "my-app"
# Or use an ecosystem file for full control
pm2 start ecosystem.config.js
// ecosystem.config.js
module.exports = {
apps: [{
name: 'my-app',
script: './server.js',
instances: 'max', // Use all CPU cores
exec_mode: 'cluster', // Cluster mode for load balancing
max_memory_restart: '500M', // Restart if memory exceeds 500MB
env_production: {
NODE_ENV: 'production',
PORT: 3000,
},
// Logging
log_file: '/var/log/pm2/app.log',
error_file: '/var/log/pm2/error.log',
merge_logs: true,
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
// Graceful restart
kill_timeout: 5000,
listen_timeout: 10000,
// Auto-restart on memory leak
max_restarts: 10,
min_uptime: '10s',
}]
};
# Essential PM2 commands
pm2 status # Show running processes
pm2 monit # Real-time monitoring dashboard
pm2 logs # View all logs
pm2 reload my-app # Zero-downtime reload
pm2 save # Save process list
pm2 startup # Generate startup script
Node.js Memory Management
# Increase V8 heap size (default is ~1.7 GB)
node --max-old-space-size=4096 server.js
# Monitor memory usage
node --expose-gc -e "console.log(process.memoryUsage())"
Python Django/Flask Requirements
WSGI vs ASGI
| Feature | WSGI (Gunicorn) | ASGI (Uvicorn/Daphne) |
| Protocol | Synchronous HTTP | Async HTTP, WebSockets |
| Best For | Traditional Django apps | Django Channels, FastAPI |
| Workers | sync, gthread, gevent | async event loop |
| Performance | Good for I/O-bound | Excellent for async I/O |
Gunicorn Worker Calculation
# Formula: workers = (2 * CPU_CORES) + 1
# For a 4-core server: (2 * 4) + 1 = 9 workers
# Install Gunicorn
pip install gunicorn
# Run Django with Gunicorn
gunicorn myproject.wsgi:application
--workers 9
--worker-class gthread
--threads 4
--bind 0.0.0.0:8000
--timeout 120
--max-requests 1000
--max-requests-jitter 50
--access-logfile /var/log/gunicorn/access.log
--error-logfile /var/log/gunicorn/error.log
# /etc/systemd/system/gunicorn.service
[Unit]
Description=Gunicorn daemon for Django
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/myproject
ExecStart=/var/www/myproject/venv/bin/gunicorn
myproject.wsgi:application
--workers 9
--bind unix:/run/gunicorn.sock
Restart=always
[Install]
WantedBy=multi-user.target
ASGI with Uvicorn (for FastAPI or Django Channels)
# Install
pip install uvicorn[standard]
# Run FastAPI
uvicorn main:app
--host 0.0.0.0
--port 8000
--workers 4
--loop uvloop
--http httptools
--access-log
PHP Laravel Requirements
PHP-FPM Tuning
# /etc/php/8.3/fpm/pool.d/www.conf
; Process manager: static, dynamic, or ondemand
pm = dynamic
; Max children = Available RAM / Average PHP process size
; Example: 8 GB RAM, ~50 MB per process = 160 max children
; Reserve RAM for OS, DB, etc. so use ~100
pm.max_children = 100
pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 30
pm.max_requests = 500
; Timeouts
request_terminate_timeout = 60s
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm/slow.log
OPcache Configuration
# /etc/php/8.3/fpm/conf.d/10-opcache.ini
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0 ; Set to 0 in production
opcache.revalidate_freq=0
opcache.save_comments=1
opcache.jit_buffer_size=128M
opcache.jit=1255 ; Enable JIT for PHP 8.x
# Laravel optimization commands for production
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
php artisan optimize
composer install --optimize-autoloader --no-dev
Java Spring Boot Requirements
JVM Heap Sizing
# Rule: Set heap to 50-75% of available RAM
# For an 8 GB server:
java -Xms4g -Xmx6g -jar app.jar
# Full production JVM flags
java
-Xms4g
-Xmx6g
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:+UseStringDeduplication
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/java/heapdump.hprof
-XX:+PrintGCDetails
-Xlog:gc*:file=/var/log/java/gc.log
-jar app.jar
GC Tuning Comparison
| GC Algorithm | Best For | Heap Size | Latency |
| G1GC (default) | General purpose | 4-32 GB | Low-medium |
| ZGC | Ultra-low latency | Up to 16 TB | Sub-ms pauses |
| Shenandoah | Low latency | Any size | Very low pauses |
| ParallelGC | Throughput | Any size | Higher pauses, more throughput |
Ruby on Rails — Puma Configuration
# config/puma.rb
workers ENV.fetch("WEB_CONCURRENCY") { 4 }
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
threads threads_count, threads_count
preload_app!
port ENV.fetch("PORT") { 3000 }
environment ENV.fetch("RAILS_ENV") { "production" }
on_worker_boot do
ActiveRecord::Base.establish_connection
end
# Worker timeout
worker_timeout 60
# Bind to socket for nginx
bind "unix:///var/run/puma.sock"
Go Applications
Go applications are compiled to native binaries and are extremely efficient. A single Go binary can handle 100,000+ concurrent connections with minimal memory.
| Traffic Level | CPU | RAM | Notes |
| Small API | 1 vCPU | 512 MB | Go is incredibly efficient |
| Medium service | 2 vCPU | 1-2 GB | Handles 50k+ req/s easily |
| High-traffic | 4+ vCPU | 4+ GB | Millions of requests per second possible |
# Build optimized binary
CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o server .
# Set GOMAXPROCS (defaults to all CPUs)
GOMAXPROCS=4 ./server
Database Server Tuning
MySQL — my.cnf Tuning
# /etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]
# InnoDB Settings (most important)
innodb_buffer_pool_size = 4G # 50-70% of total RAM
innodb_buffer_pool_instances = 4 # 1 per GB of buffer pool
innodb_log_file_size = 512M
innodb_flush_log_at_trx_commit = 2 # Better performance (slight risk)
innodb_flush_method = O_DIRECT
innodb_io_capacity = 2000
innodb_io_capacity_max = 4000
# Connection Settings
max_connections = 500
wait_timeout = 600
interactive_timeout = 600
# Query Cache (disabled in MySQL 8.0+, use ProxySQL)
# query_cache_type = 0
# Logging
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
# Binary Logging (for replication)
server-id = 1
log_bin = /var/log/mysql/mysql-bin
binlog_expire_logs_seconds = 604800
PostgreSQL — postgresql.conf Tuning
# /etc/postgresql/16/main/postgresql.conf
# Memory (for 16 GB RAM server)
shared_buffers = 4GB # 25% of total RAM
effective_cache_size = 12GB # 75% of total RAM
work_mem = 64MB # Per-operation sort memory
maintenance_work_mem = 1GB
# WAL Settings
wal_buffers = 64MB
max_wal_size = 4GB
min_wal_size = 1GB
checkpoint_completion_target = 0.9
# Connections
max_connections = 200
# Use PgBouncer for connection pooling in production
# Query Planner
random_page_cost = 1.1 # SSD storage
effective_io_concurrency = 200 # SSD storage
default_statistics_target = 100
# Logging
log_min_duration_statement = 1000 # Log queries > 1 second
log_checkpoints = on
log_connections = on
log_disconnections = on
MongoDB Requirements
# /etc/mongod.conf
storage:
dbPath: /var/lib/mongodb
wiredTiger:
engineConfig:
cacheSizeGB: 4 # 50% of RAM minus 1 GB
collectionConfig:
blockCompressor: snappy
net:
port: 27017
bindIp: 127.0.0.1
operationProfiling:
slowOpThresholdMs: 100
mode: slowOp
Caching — Redis and Memcached Sizing
| Feature | Redis | Memcached |
| Data structures | Strings, Lists, Sets, Hashes, Sorted Sets | Strings only |
| Persistence | RDB + AOF | None |
| Clustering | Redis Cluster | Client-side sharding |
| Memory efficiency | Higher overhead | Better for simple caching |
| Use case | Sessions, queues, real-time data | Simple page/query caching |
# Redis memory configuration
# /etc/redis/redis.conf
maxmemory 2gb
maxmemory-policy allkeys-lru
# Monitor Redis memory
redis-cli INFO memory
Cloud Provider Comparison
| Provider | 2 vCPU / 4 GB | 4 vCPU / 8 GB | Managed DB | Best For |
| AWS EC2 | ~$35/mo (t3.medium) | ~$70/mo (t3.xlarge) | RDS from $15/mo | Enterprise, full ecosystem |
| DigitalOcean | $24/mo | $48/mo | From $15/mo | Startups, simplicity |
| Hetzner | €6.90/mo (CX22) | €14.90/mo (CX32) | Not available | Best price/performance |
| Vercel | $20/mo (Pro) | N/A (serverless) | Vercel Postgres | Frontend, Next.js |
| Railway | Usage-based ($5+) | Usage-based | Built-in | Rapid deployment |
| Fly.io | $10-30/mo | $30-60/mo | Fly Postgres | Edge deployment |
VPS vs Managed vs Serverless Cost Analysis
| Model | Monthly Cost (Medium App) | Scaling | Maintenance | Best For |
| VPS (Hetzner) | $15-50 | Manual | High (you manage everything) | Budget projects, full control |
| Managed (Railway, Render) | $25-100 | Automatic | Low | Small teams, MVPs |
| AWS (EC2 + RDS) | $80-300 | Auto Scaling Groups | Medium | Enterprise, compliance |
| Serverless (Lambda) | $0-50 (usage-based) | Infinite | Minimal | Variable traffic, APIs |
Load Testing with k6
# Install k6
brew install k6 # macOS
choco install k6 # Windows
// load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
{ duration: '30s', target: 50 }, // Ramp up to 50 users
{ duration: '1m', target: 100 }, // Ramp up to 100 users
{ duration: '2m', target: 100 }, // Stay at 100 users
{ duration: '30s', target: 0 }, // Ramp down
],
thresholds: {
http_req_duration: ['p(95)<500'], // 95% of requests under 500ms
http_req_failed: ['rate<0.01'], // Less than 1% errors
},
};
export default function () {
const res = http.get('https://myapp.com/api/products');
check(res, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
});
sleep(1);
}
# Run the test
k6 run load-test.js
# Run with more virtual users
k6 run --vus 200 --duration 5m load-test.js
Capacity Planning Formulas
# Required Servers Formula
Servers = (Peak_Concurrent_Users * Avg_Request_Duration_Seconds) / (Requests_Per_Second_Per_Server)
# Memory Formula for Web Servers
Required_RAM = (Worker_Count * Avg_Worker_Memory) + OS_Overhead + DB_Cache
# Example: Node.js API serving 10,000 concurrent users
# Avg response time: 100ms = 0.1s
# Single server handles ~1,000 req/s with PM2 cluster
# Servers needed: (10,000 * 0.1) / 1,000 = 1 server (with headroom, use 2-3)
# Example: PHP Laravel app
# Workers: 50, Average memory per worker: 50 MB
# Required_RAM = (50 * 50 MB) + 1 GB (OS) + 2 GB (DB cache) = 5.5 GB → Use 8 GB server
Monitoring Setup with Prometheus and Grafana
# docker-compose.monitoring.yml
version: '3.8'
services:
prometheus:
image: prom/prometheus:latest
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
image: grafana/grafana:latest
ports:
- "3001:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin123
volumes:
- grafana-data:/var/lib/grafana
node-exporter:
image: prom/node-exporter:latest
ports:
- "9100:9100"
volumes:
grafana-data:
# prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'node'
static_configs:
- targets: ['node-exporter:9100']
- job_name: 'app'
static_configs:
- targets: ['app:3000']
metrics_path: /metrics
Quick Reference — Server Sizing by Stack
| Stack | Min CPU | Min RAM | Key Tuning Parameter |
| Node.js + PM2 | 2 vCPU | 2 GB | PM2 cluster instances = CPU cores |
| Python + Gunicorn | 2 vCPU | 4 GB | Workers = (2 × cores) + 1 |
| PHP + PHP-FPM | 2 vCPU | 4 GB | pm.max_children = RAM / 50MB |
| Java + Spring Boot | 2 vCPU | 4 GB | -Xmx = 50-75% of RAM |
| Ruby + Puma | 2 vCPU | 2 GB | Workers × threads per worker |
| Go | 1 vCPU | 512 MB | GOMAXPROCS, virtually no tuning needed |
| MySQL | 2 vCPU | 4 GB | innodb_buffer_pool_size = 50-70% RAM |
| PostgreSQL | 2 vCPU | 4 GB | shared_buffers = 25% RAM |
| Redis | 1 vCPU | 1 GB | maxmemory + eviction policy |