Server Performance Tuning Checklist

A practical, hands-on checklist for tuning a Linux server: profile first to find the real bottleneck, then enable BBR, adjust sysctl and ulimit, tune Nginx and your database, add caching and a CDN — verifying each change as you go.

The first rule of tuning isn't "change a setting" — it's "measure first." The bottleneck on any server or VPS could be CPU, memory, disk IO, or the network, and blindly copying "magic" values from a forum often does nothing, or even makes things slower. Treat every item below as a small experiment: hypothesize, change, verify.

Step 1: Monitor first, find the real bottleneck

Before touching anything, see what the system is actually busy doing.

top            # or htop — CPU, memory, load average
vmstat 1       # context switches, swap, IO wait
iostat -x 1    # disk IO utilization (%util near 100 means disk-bound)
ss -s          # summary of socket/connection counts

If the CPU is idle but responses are slow, the bottleneck is usually disk or the database. If connections are maxed out, then look at network settings. Tuning without data is just guessing.

Step 2: Enable BBR congestion control

BBR noticeably improves throughput on high-latency, lossy links — ideal for cross-region traffic.

echo "net.core.default_qdisc=fq" | sudo tee -a /etc/sysctl.conf
echo "net.ipv4.tcp_congestion_control=bbr" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
sysctl net.ipv4.tcp_congestion_control   # should print: bbr

Step 3: Tune sysctl kernel parameters

Append what you need to /etc/sysctl.conf, then apply with sudo sysctl -p:

  • fs.file-max = 1000000 — system-wide maximum file handles
  • net.core.somaxconn = 65535 — larger listen queue (essential under high concurrency)
  • net.ipv4.tcpmaxsynbacklog = 65535 — SYN half-open queue
  • net.ipv4.tcptwreuse = 1 — reuse sockets stuck in TIMEWAIT
  • net.ipv4.iplocalportrange = 1024 65535 — widen the usable port range

Raise a value only once load testing shows that queue is actually overflowing — don't paste them all at once.

Step 4: Raise the ulimit open-file count

You can bump somaxconn all you want, but a low per-process nofile will still throw Too many open files.

# /etc/security/limits.conf
* soft nofile 65535
* hard nofile 65535

For systemd-managed services, add LimitNOFILE=65535 to the unit file. Confirm with ulimit -n.

Step 5: Tune Nginx

worker_processes auto;              # usually one per CPU core
worker_connections 10240;           # concurrent connections per worker
keepalive_timeout 65;               # reuse connections, cut handshakes
gzip on;                            # compress text responses

Run nginx -t to validate, then reload.

Step 6: Size the database buffer

The database is often the real bottleneck. Give its buffer enough memory — on a dedicated box, 50%–70% of physical RAM is a reasonable start: tune innodbbufferpoolsize for MySQL/MariaDB, or sharedbuffers and effectivecachesize for PostgreSQL. Watch the cache hit ratio afterward, and don't push memory so high that the box starts swapping.

Step 7: Add a cache layer and a CDN

At the application layer, put Redis or Memcached in front of hot queries and sessions to take load straight off the database. Hand static assets (images, JS, CSS) to a CDN — it both lightens the origin and speeds up access worldwide.

Summary

Tuning is a loop: monitor to find the bottleneck → change one thing → measure again to verify → keep it only if it helped. Move one variable at a time and record the metrics before and after. The right values depend on your machine's specs and your workload; there is no universal "optimal config." Hold that discipline, and your server or VPS will stay both fast and stable.