| |

LFCE 49 ๐Ÿง Nginx โ€” Performance Tuning Basics

โฑ๏ธ Estimated read: 13 min | |

Nginx’s default configuration is conservative. It is designed to work on any hardware, from a single-core virtual machine to a large multi-core server, without requiring the administrator to know the CPU count or the memory available. That conservatism is a safety margin, not an optimization. On a production server, the default worker count, connection limit, and buffer sizes leave performance on the table. The previous chapter covered TLS configuration; this chapter covers the tuning that determines how many clients the server can handle, how efficiently it serves static files, and how much bandwidth it consumes.

Tuning is not a single change. It is a set of adjustments at three layers: the worker processes that accept connections, the event loop that manages them, and the HTTP-level directives that govern file transmission, compression, and connection reuse. The changes must be validated with nginx -t and applied with nginx -s reload, and they must be measured against the server’s actual workload rather than copied blindly from a guide .

Key point: The maximum number of simultaneous connections is approximately worker_processes ร— worker_connections. worker_processes auto; sets the process count to the number of CPU cores. worker_connections sets the per-process connection limit, and worker_rlimit_nofile must be raised to match, or the operating system will refuse to open the sockets the configuration allows. The system’s ulimit -n must also be raised for the worker processes to inherit the higher limit .


Why performance tuning requires layered adjustments

The worker-process problem.

Nginx uses a master process and one or more worker processes. The workers accept connections, process requests, and send responses. A single worker process can handle thousands of connections because it uses an event-driven, non-blocking model, but it can only use one CPU core at a time. On a server with four cores, a single worker leaves three cores idle. worker_processes auto; sets the worker count to the number of detected cores, allowing the server to use all available CPU capacity . Setting it higher than the core count does not increase throughput; it increases context switching.

The file-descriptor problem.

Each connection requires a file descriptor. The default limit on many Linux systems is 1024, which caps the server at roughly 1024 simultaneous connections regardless of what worker_connections says. The worker_rlimit_nofile directive raises the limit for the worker processes, but it must be matched by the system-wide limit in /etc/security/limits.conf. If the system limit is lower than the nginx limit, the worker cannot open the file descriptors the configuration permits .

The transfer-efficiency problem.

Serving static files involves copying data from the filesystem to the kernel’s socket buffer and then to the network. The sendfile directive allows the kernel to transfer the file directly from the filesystem to the socket, bypassing the user-space copy. tcp_nopush and tcp_nodelay control how the kernel buffers and transmits the data. These directives do not change the number of connections, but they change how efficiently each connection is served, which matters for static content and large downloads .


a. Worker processes and connection limits

The worker_processes and worker_connections directives determine the theoretical maximum concurrency. The worker_rlimit_nofile directive determines whether the operating system permits it.

# /etc/nginx/nginx.conf
worker_processes auto;                  # One worker per CPU core
worker_rlimit_nofile 65535;             # Raise the file descriptor limit

events {
    worker_connections 4096;            # Max connections per worker
    use epoll;                          # Linux event model
    multi_accept on;                    # Accept multiple connections at once
}

The use epoll; directive selects the Linux event notification mechanism. It is the default on Linux, but setting it explicitly documents the intent. The multi_accept on; directive allows a worker to accept all new connections in a single event loop iteration rather than one at a time, which improves connection acceptance under load .

The maximum concurrency calculation is approximate. With worker_processes auto on a four-core server and worker_connections 4096, the theoretical maximum is 16,384 connections. In practice, each connection consumes memory and file descriptors, and the actual limit is constrained by the server’s RAM and the system’s network stack. The worker_connections value should be raised gradually and measured, not set to an arbitrary high number .

# Verify the system file descriptor limit
ulimit -n

# The nginx worker processes inherit this limit
# If it is lower than worker_rlimit_nofile, raise it in /etc/security/limits.conf
# www-data soft nofile 65535
# www-data hard nofile 65535

b. HTTP-level transfer and connection directives

The HTTP block contains the directives that govern file transmission, connection reuse, and buffer sizes. These settings affect the efficiency of each connection rather than the number of connections.

http {
    sendfile on;                        # Kernel-level file transfer
    tcp_nopush on;                      # Send headers in one packet
    tcp_nodelay on;                     # Don't buffer small packets

    keepalive_timeout 65;               # Idle connection timeout
    keepalive_requests 1000;            # Max requests per connection

    client_body_timeout 12;             # Slow client protection
    client_header_timeout 12;
    send_timeout 10;

    server_tokens off;                  # Hide version string
    types_hash_max_size 2048;
}

The sendfile on; directive is the most impactful single setting for static file serving. It reduces CPU usage and memory bandwidth by eliminating the user-space copy. The tcp_nopush on; directive, when combined with sendfile, allows nginx to send the response headers in a single packet before the file data, which reduces the number of packets on the wire .

The keepalive_timeout directive controls how long an idle connection remains open. A higher value reduces the number of TCP handshakes for clients that make multiple requests, but it holds server resources for idle connections. The default is 75 seconds; a value between 30 and 65 seconds is a common balance. The keepalive_requests directive sets the maximum number of requests a single connection can serve before it is closed. Raising it to 1000 or higher allows a connection to serve more requests before the client must reconnect .

The timeout directives protect against slow clients. A client that opens a connection and sends data slowly can hold a worker process for an extended period. The client_body_timeout and client_header_timeout directives close the connection if the client does not send the complete request within the specified time .


c. Gzip compression and static asset caching

Gzip compression reduces the size of text-based responses. Static asset caching tells the browser to store files locally so that subsequent requests do not reach the server at all.

http {
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 5;                  # Balance CPU vs compression
    gzip_min_length 1024;               # Don't compress tiny files
    gzip_types
        text/plain
        text/css
        application/json
        application/javascript
        text/xml
        application/xml
        image/svg+xml;
}

server {
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2|svg)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
        access_log off;
    }
}

The gzip_comp_level directive ranges from 1 to 9. Higher levels compress more but consume more CPU. Level 5 or 6 is the common balance for production servers . The gzip_min_length directive prevents compression of very small files, where the compression overhead exceeds the savings. The gzip_types directive lists the MIME types that are compressed; images and videos are already compressed and should not be included .

The expires directive in the static asset location sets the Expires and Cache-Control headers. A value of 30d or 365d tells the browser that the file is valid for that duration. The immutable directive in the Cache-Control header tells the browser that the file will never change, which allows it to skip revalidation entirely. The access_log off; directive disables logging for static assets, which reduces disk I/O on high-traffic servers .


Complete Example Session

# ============================================
# PART 1: INSPECT THE CURRENT CONFIGURATION
# ============================================
grep -E "worker_processes|worker_connections|sendfile|gzip" /etc/nginx/nginx.conf
# The default values are conservative

# ============================================
# PART 2: CHECK THE SYSTEM FILE DESCRIPTOR LIMIT
# ============================================
ulimit -n
# Output: 1024 (the default, which is too low for high concurrency)

# ============================================
# PART 3: RAISE THE SYSTEM LIMIT
# ============================================
sudo tee -a /etc/security/limits.conf > /dev/null <<'EOF'
www-data soft nofile 65535
www-data hard nofile 65535
EOF

# ============================================
# PART 4: EDIT THE NGINX CONFIGURATION
# ============================================
sudo tee /etc/nginx/nginx.conf > /dev/null <<'EOF'
worker_processes auto;
worker_rlimit_nofile 65535;

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}

http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    keepalive_timeout 65;
    keepalive_requests 1000;

    client_body_timeout 12;
    client_header_timeout 12;
    send_timeout 10;

    server_tokens off;

    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 5;
    gzip_min_length 1024;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;

    include /etc/nginx/mime.types;
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}
EOF

# ============================================
# PART 5: ADD STATIC ASSET CACHING
# ============================================
# In the site's server block or a location block
# location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2|svg)$ {
#     expires 30d;
#     add_header Cache-Control "public, immutable";
#     access_log off;
# }

# ============================================
# PART 6: VALIDATE THE CONFIGURATION
# ============================================
sudo nginx -t
# Output: syntax is ok, test is successful

# ============================================
# PART 7: RELOAD NGINX
# ============================================
sudo systemctl reload nginx

# ============================================
# PART 8: VERIFY THE WORKER COUNT
# ============================================
ps aux | grep "nginx: worker" | grep -v grep | wc -l
# Output: the number of CPU cores

# ============================================
# PART 9: VERIFY GZIP IS ACTIVE
# ============================================
curl -H "Accept-Encoding: gzip" -I http://localhost
# Output includes: Content-Encoding: gzip

# ============================================
# PART 10: MEASURE BEFORE AND AFTER
# ============================================
# Use a load testing tool such as ab, wrk, or hey
# ab -n 10000 -c 100 http://localhost/
# Compare requests per second and latency before and after tuning

The ten parts demonstrate the tuning workflow: inspect, raise the system limit, edit the configuration, add caching, validate, reload, verify the worker count, verify gzip, and measure. The critical step is Part 3: raising the system limit is necessary before nginx can use the higher worker_rlimit_nofile value. Without it, the worker processes inherit the old limit and the connection ceiling remains unchanged.


Quick Reference

Worker and Event Directives

DirectiveRecommended ValuePurpose
worker_processesautoOne worker per CPU core
worker_rlimit_nofile65535File descriptor limit per worker
worker_connections4096 or higherConnections per worker
useepollLinux event model
multi_acceptonAccept multiple connections at once

HTTP Transfer Directives

DirectiveRecommended ValuePurpose
sendfileonKernel-level file transfer
tcp_nopushonSend headers in one packet
tcp_nodelayonDisable Nagle for small packets
keepalive_timeout30โ€“65Idle connection timeout
keepalive_requests1000Max requests per connection

Gzip and Caching

DirectiveRecommended ValuePurpose
gziponEnable compression
gzip_comp_level5 or 6Balance CPU vs compression
gzip_min_length1024Skip tiny files
gzip_typesText MIME typesCompress only text
expires30d or 365dStatic asset cache duration

Best Practices

โœ… Do This:

# Set worker_processes to auto
worker_processes auto;                                      # โœ…

# Raise the file descriptor limit
worker_rlimit_nofile 65535;                                 # โœ…

# Enable sendfile for static files
sendfile on;                                                # โœ…

# Enable gzip for text resources
gzip on; gzip_comp_level 5;                                 # โœ…

# Cache static assets with long expiry
expires 30d; add_header Cache-Control "public, immutable";   # โœ…

# Validate before reloading
nginx -t                                                    # โœ…

# Measure the impact with a load testing tool
ab -n 10000 -c 100 http://localhost/                        # โœ…

โŒ Don’t Do This:

# Set worker_processes higher than the core count
worker_processes 16;    # On a 4-core server โ†’ context switching # โŒ

# Set worker_connections without raising worker_rlimit_nofile
worker_connections 10000;    # System limit caps it at 1024     # โŒ

# Compress images with gzip
gzip_types image/jpeg image/png;    # Already compressed         # โŒ

# Set gzip_comp_level to 9 for all traffic
gzip_comp_level 9;    # High CPU cost for marginal gain        # โŒ

# Disable keepalive entirely
keepalive_timeout 0;    # Every request requires a new handshake # โŒ

# Apply tuning without measuring
# Changes may not improve the specific workload                   # โŒ

Common Pitfalls

PitfallWhy It HappensFix
Connection ceiling lower than expectedSystem ulimit -n is too lowRaise limit in /etc/security/limits.conf
Workers exceed CPU coresworker_processes set manually too highUse auto
High CPU with gzipgzip_comp_level too highReduce to 5 or 6
Static assets not cachedNo expires headerAdd expires and Cache-Control
Slow client holds connectionsNo timeout directivesSet client_body_timeout and client_header_timeout
Configuration error after tuningMissing semicolon or invalid directivenginx -t reports the line

Real-World Examples

1. Worker auto-detection

worker_processes auto;

2. Connection limit

worker_connections 4096;

3. File descriptor limit

worker_rlimit_nofile 65535;

4. Event model

use epoll; multi_accept on;

5. Efficient file transfer

sendfile on; tcp_nopush on; tcp_nodelay on;

6. Keepalive settings

keepalive_timeout 65; keepalive_requests 1000;

7. Gzip compression

gzip on; gzip_comp_level 5; gzip_min_length 1024;

8. Static asset caching

location ~* \.(css|js|jpg|png)$ { expires 30d; add_header Cache-Control "public, immutable"; }

9. Validate configuration

nginx -t

10. Measure performance

ab -n 10000 -c 100 http://localhost/

Visual

The Concurrency Formula

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  MAX CONNECTIONS โ‰ˆ worker_processes ร— worker_connections     โ”‚
โ”‚                                                              โ”‚
โ”‚  4 CPU cores                                                 โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ worker_processes = 4 (auto)                            โ”‚  โ”‚
โ”‚  โ”‚ worker_connections = 4096                              โ”‚  โ”‚
โ”‚  โ”‚                                                        โ”‚  โ”‚
โ”‚  โ”‚ Theoretical max = 4 ร— 4096 = 16,384 connections        โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                                                              โ”‚
โ”‚  BUT:                                                         โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ worker_rlimit_nofile must be โ‰ฅ worker_connections      โ”‚  โ”‚
โ”‚  โ”‚ System ulimit -n must be โ‰ฅ worker_rlimit_nofile        โ”‚  โ”‚
โ”‚  โ”‚ Otherwise the system limit caps the actual connections โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Three Layers of Tuning

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  TUNING IS LAYERED                                           โ”‚
โ”‚                                                              โ”‚
โ”‚  Layer 1: Process and connection                             โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ worker_processes, worker_connections,                  โ”‚  โ”‚
โ”‚  โ”‚ worker_rlimit_nofile, use epoll                        โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                           โ”‚                                  โ”‚
โ”‚                           โ–ผ                                  โ”‚
โ”‚  Layer 2: Transfer and connection reuse                      โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ sendfile, tcp_nopush, tcp_nodelay,                     โ”‚  โ”‚
โ”‚  โ”‚ keepalive_timeout, keepalive_requests                  โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                           โ”‚                                  โ”‚
โ”‚                           โ–ผ                                  โ”‚
โ”‚  Layer 3: Bandwidth and caching                              โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ gzip, expires, Cache-Control, access_log off           โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                                                              โ”‚
โ”‚  Each layer addresses a different bottleneck.                โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Static Asset Caching Headers

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  CACHE-CONTROL AND EXPIRES REDUCE SERVER LOAD                โ”‚
โ”‚                                                              โ”‚
โ”‚  Without caching:                                            โ”‚
โ”‚  Browser โ”€โ”€โ–ถ Server: "Give me style.css"                     โ”‚
โ”‚  Server โ”€โ”€โ–ถ Browser: "Here it is"                            โ”‚
โ”‚  (every request reaches the server)                          โ”‚
โ”‚                                                              โ”‚
โ”‚  With caching:                                               โ”‚
โ”‚  Browser: "I have style.css, expires in 30 days"             โ”‚
โ”‚  (subsequent requests use the local copy, no server contact) โ”‚
โ”‚                                                              โ”‚
โ”‚  Headers:                                                    โ”‚
โ”‚  expires 30d;                                                โ”‚
โ”‚  add_header Cache-Control "public, immutable";               โ”‚
โ”‚                                                              โ”‚
โ”‚  The immutable directive tells the browser it can skip       โ”‚
โ”‚  revalidation entirely for the cached file.                  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
Worker processesworker_processes auto;
Per-worker connectionsworker_connections 4096;
File descriptor limitworker_rlimit_nofile 65535;
System limit/etc/security/limits.conf
Event modeluse epoll; multi_accept on;
File transfersendfile on; tcp_nopush on; tcp_nodelay on;
Keepalivekeepalive_timeout 65; keepalive_requests 1000;
Gzipgzip on; gzip_comp_level 5; gzip_min_length 1024;
Static cachingexpires 30d; add_header Cache-Control "public, immutable";
Validationnginx -t
Measurementab, wrk, or hey

Key takeaways:

  • The concurrency ceiling is worker_processes ร— worker_connections. worker_processes auto matches the core count, and worker_connections sets the per-worker limit. The system’s file descriptor limit must be raised to match, or the configuration is capped by the operating system .
  • sendfile, tcp_nopush, and tcp_nodelay improve transfer efficiency. These directives reduce CPU usage and packet count for static files. They do not change the connection count, but they change how efficiently each connection is served .
  • Keepalive reduces handshake overhead. keepalive_timeout and keepalive_requests control how long a connection stays open and how many requests it can serve. A higher keepalive_requests value is appropriate for high-traffic servers .
  • Gzip reduces bandwidth for text resources. The compression level should balance CPU cost against savings. Level 5 or 6 is a common production value, and gzip_min_length prevents compression of files where the overhead exceeds the benefit .
  • Static asset caching eliminates repeat requests. The expires and Cache-Control headers tell the browser to store the file locally. The immutable directive allows the browser to skip revalidation entirely .
  • Measure before and after. The values in this chapter are starting points, not universal recommendations. The actual impact depends on the server’s hardware, the workload, and the network. A load testing tool and the access logs provide the data that determines whether the tuning achieved its goal .

Remember: Nginx performance tuning is a matter of matching the configuration to the hardware and the workload. The defaults are safe but conservative. worker_processes auto uses the available cores. worker_connections and worker_rlimit_nofile raise the connection ceiling, but only if the system’s file descriptor limit is raised to match. sendfile and the TCP directives make each connection more efficient. Gzip and static caching reduce bandwidth and server load. Validate with nginx -t, reload with systemctl reload nginx, and measure with a load testing tool. The configuration that improves one server may not improve another, because the bottleneck is different.


Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!