Ajie Logo Ajie.
Navigation

© 2026 Ajie Kusumadhany.

Back to Articles

Why Your Nginx Config Is Probably Slowing You Down Kenapa Konfigurasi Nginx Anda Mungkin Memperlambat Performa

Ajie Ajie Kusumadhany
Jul 04, 2026 9 min read
Why Your Nginx Config Is Probably Slowing You Down Kenapa Konfigurasi Nginx Anda Mungkin Memperlambat Performa

I watched a developer spend three days optimizing database queries, only to discover that their Nginx configuration was the real bottleneck all along.

The application could handle 500 requests per second in testing. In production, it barely managed 50.

Here's the uncomfortable truth: most of us treat Nginx like a mystical black box. We copy-paste Stack Overflow answers, hope for the best, and never look back.

Until everything slows to a crawl.

The Default Config Trap

When you install Nginx, you get a default configuration that's designed for safety, not speed.

It's like buying a sports car with the parking brake on. Sure, it works. But you're nowhere near peak performance.

The most common performance killer? worker_processes set to 1. This means your multi-core server is using just one CPU core for all incoming requests.

One. Single. Core.

Here's what a typical default config looks like:

worker_processes 1;
events {
    worker_connections 768;
}

This setup can't handle modern web traffic. Period.

Worker Processes: Your First Performance Unlock

The worker_processes directive tells Nginx how many worker processes to spawn.

Each worker can handle thousands of connections simultaneously thanks to Nginx's event-driven architecture.

The magic number? Set it to auto. This tells Nginx to create one worker per CPU core.

worker_processes auto;

On a 4-core server, this instantly quadruples your processing capacity.

But there's more to the story.

Worker Connections: The Hidden Ceiling

Each worker process has a maximum number of simultaneous connections it can handle.

The default is usually 768 or 1024. That sounds like a lot until you do the math.

If you're using Nginx as a reverse proxy (and you probably are), each client request requires TWO connections: one from the client to Nginx, and one from Nginx to your backend.

So your effective limit is half of worker_connections.

With 1024 worker connections and 4 workers, you can theoretically handle 2,048 simultaneous clients. But as a reverse proxy, that drops to about 1,024.

Here's the fix:

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

The epoll directive uses Linux's efficient event notification mechanism. The multi_accept on tells workers to accept as many connections as possible at once.

Buffer Sizes: Where Memory Meets Speed

Nginx uses buffers to store request and response data temporarily.

Too small, and Nginx writes to disk constantly. Too large, and you waste memory.

Most developers never touch these settings. That's a mistake.

Buffer Type Default Recommended Purpose
client_body_buffer_size 8k/16k 16k POST data buffer
client_header_buffer_size 1k 1k Request headers
client_max_body_size 1m 20m Max upload size
large_client_header_buffers 4 8k 4 16k Large headers/cookies

If you're handling file uploads, adjust client_max_body_size accordingly. A 1MB limit will reject any reasonable file upload.

For APIs with large request bodies, increase client_body_buffer_size.

Keepalive: The Misunderstood Hero

HTTP keepalive allows multiple requests over a single TCP connection.

Opening a new TCP connection is expensive. There's the three-way handshake, SSL/TLS negotiation if you're using HTTPS, and TCP slow start.

All that overhead for every single request adds up fast.

Here's the catch: you need to configure keepalive in TWO places.

First, for client connections:

keepalive_timeout 65;
keepalive_requests 100;

This tells Nginx to keep client connections open for 65 seconds and allow up to 100 requests per connection.

But most developers forget the second part: upstream keepalive.

upstream backend {
    server 127.0.0.1:3000;
    keepalive 32;
}

location / {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
}

Without this, Nginx opens a fresh connection to your backend for every single request. Your backend spends more time establishing connections than processing requests.

Gzip Compression: Free Performance Wins

Enabling gzip compression can reduce response sizes by 70% or more.

Yet many production servers run without it because it's not enabled by default in some distributions.

gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript 
           application/json application/javascript application/xml+rss 
           application/rss+xml font/truetype font/opentype 
           application/vnd.ms-fontobject image/svg+xml;

The gzip_comp_level is a trade-off between CPU usage and compression ratio. Level 6 is the sweet spot for most use cases.

Going higher than 6 provides diminishing returns while increasing CPU load significantly.

Caching: The Nuclear Option

If your backend generates the same response repeatedly, you're wasting resources.

Nginx can cache responses and serve them directly without touching your backend.

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m 
                 max_size=1g inactive=60m use_temp_path=off;

location / {
    proxy_cache my_cache;
    proxy_cache_valid 200 60m;
    proxy_cache_valid 404 10m;
    proxy_cache_use_stale error timeout http_500 http_502 http_503;
    proxy_cache_background_update on;
    proxy_cache_lock on;
    
    proxy_pass http://backend;
}

The proxy_cache_use_stale directive is particularly clever. If your backend goes down, Nginx serves stale cached content instead of showing errors.

Your users see slightly outdated content instead of a 502 Bad Gateway.

Static File Optimization

If Nginx is serving static files, these settings dramatically improve performance:

location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    access_log off;
    
    open_file_cache max=1000 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;
}

The open_file_cache keeps file descriptors in memory. Nginx doesn't have to hit the filesystem for every request.

Disabling access_log for static assets removes unnecessary disk I/O.

SSL/TLS: Speed Without Sacrificing Security

SSL/TLS handshakes are computationally expensive.

But you can optimize them without compromising security:

ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;

ssl_stapling on;
ssl_stapling_verify on;

The session cache allows clients to resume previous SSL sessions without a full handshake.

This alone can reduce SSL overhead by 80% for returning visitors.

Rate Limiting: Protect Your Backend

Even a perfectly configured Nginx can't save you from traffic spikes that overwhelm your backend.

Rate limiting protects your infrastructure:

limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

location /api {
    limit_req zone=api_limit burst=20 nodelay;
    proxy_pass http://backend;
}

This allows 10 requests per second per IP, with a burst allowance of 20 requests.

Legitimate users never notice. Attackers and misbehaving scripts get blocked before they can cause damage.

Monitoring: Know What's Actually Happening

You can't optimize what you can't measure.

Enable the stub_status module to see real-time metrics:

location /nginx_status {
    stub_status;
    allow 127.0.0.1;
    deny all;
}

This shows active connections, requests per second, and connection states.

Pair this with the access log format to track response times:

log_format detailed '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    'rt=$request_time uct="$upstream_connect_time" '
                    'uht="$upstream_header_time" urt="$upstream_response_time"';

access_log /var/log/nginx/access.log detailed;

Now you can see exactly where time is being spent: in Nginx, in network transit, or in your backend.

Pro Tips for Production

Test configuration changes before deploying. Use nginx -t to validate syntax. A typo can bring down your entire site.

Reload gracefully. Use nginx -s reload instead of restarting. This allows existing connections to complete before applying changes.

Monitor file descriptor limits. With high worker_connections, you might hit OS limits. Check with ulimit -n and increase if needed.

Use separate access logs for different applications. This makes debugging and analysis much easier when things go wrong.

Keep upstream definitions organized. As your infrastructure grows, maintain clear naming conventions for upstream blocks.

Document your changes. Future you (and your team) will thank you when troubleshooting performance issues at 2 AM.

Start conservative, then optimize. Don't throw every optimization at your config at once. Make incremental changes and measure their impact.

The difference between a default Nginx config and an optimized one can be 10x in throughput and response time.

Stop treating your web server as a black box. Understanding these fundamentals transforms Nginx from a mysterious reverse proxy into a powerful performance tool you actually control.

Saya menyaksikan seorang developer menghabiskan tiga hari mengoptimasi query database, hanya untuk menemukan bahwa konfigurasi Nginx mereka adalah bottleneck sebenarnya sejak awal.

Aplikasi tersebut bisa menangani 500 request per detik saat testing. Di production, едва bisa mencapai 50.

Inilah kebenaran yang tidak nyaman: kebanyakan dari kita memperlakukan Nginx seperti kotak hitam mistis. Kita copy-paste jawaban dari Stack Overflow, berharap yang terbaik, dan tidak pernah menoleh lagi.

Sampai semuanya melambat drastis.

Jebakan Konfigurasi Default

Saat Anda menginstal Nginx, Anda mendapat konfigurasi default yang dirancang untuk keamanan, bukan kecepatan.

Ini seperti membeli mobil sport dengan rem parkir masih aktif. Tentu berfungsi. Tapi Anda jauh dari performa puncak.

Pembunuh performa paling umum? worker_processes diset ke 1. Ini berarti server multi-core Anda hanya menggunakan satu core CPU untuk semua request yang masuk.

Satu. Core. Saja.

Begini tampilan konfigurasi default yang umum:

worker_processes 1;
events {
    worker_connections 768;
}

Setup ini tidak bisa menangani trafik web modern. Titik.

Worker Processes: Unlock Performa Pertama Anda

Direktif worker_processes memberi tahu Nginx berapa banyak proses worker yang harus di-spawn.

Setiap worker bisa menangani ribuan koneksi secara simultan berkat arsitektur event-driven Nginx.

Angka ajaibnya? Set ke auto. Ini memberitahu Nginx untuk membuat satu worker per core CPU.

worker_processes auto;

Pada server 4-core, ini langsung menggandakan kapasitas pemrosesan Anda empat kali lipat.

Tapi ada lebih banyak cerita di balik ini.

Worker Connections: Plafon Tersembunyi

Setiap proses worker memiliki jumlah maksimum koneksi simultan yang bisa ditangani.

Default-nya biasanya 768 atau 1024. Kedengarannya banyak sampai Anda menghitung matematikanya.

Jika Anda menggunakan Nginx sebagai reverse proxy (dan kemungkinan besar Anda melakukannya), setiap request client memerlukan DUA koneksi: satu dari client ke Nginx, dan satu dari Nginx ke backend Anda.

Jadi limit efektif Anda adalah setengah dari worker_connections.

Dengan 1024 worker connections dan 4 workers, Anda secara teoritis bisa menangani 2.048 client simultan. Tapi sebagai reverse proxy, itu turun menjadi sekitar 1.024.

Inilah perbaikannya:

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

Direktif epoll menggunakan mekanisme notifikasi event Linux yang efisien. multi_accept on memberitahu workers untuk menerima sebanyak mungkin koneksi sekaligus.

Ukuran Buffer: Dimana Memori Bertemu Kecepatan

Nginx menggunakan buffer untuk menyimpan data request dan response sementara.

Terlalu kecil, dan Nginx menulis ke disk terus-menerus. Terlalu besar, dan Anda membuang memori.

Kebanyakan developer tidak pernah menyentuh pengaturan ini. Itu adalah kesalahan.

Tipe Buffer Default Rekomendasi Tujuan
client_body_buffer_size 8k/16k 16k Buffer data POST
client_header_buffer_size 1k 1k Header request
client_max_body_size 1m 20m Ukuran upload maks
large_client_header_buffers 4 8k 4 16k Header/cookie besar

Jika Anda menangani upload file, sesuaikan client_max_body_size sesuai kebutuhan. Limit 1MB akan menolak upload file yang wajar.

Untuk API dengan request body besar, tingkatkan client_body_buffer_size.

Keepalive: Pahlawan yang Disalahpahami

HTTP keepalive memungkinkan multiple request melalui satu koneksi TCP.

Membuka koneksi TCP baru itu mahal. Ada three-way handshake, negosiasi SSL/TLS jika Anda menggunakan HTTPS, dan TCP slow start.

Semua overhead itu untuk setiap request tunggal cepat bertambah.

Inilah masalahnya: Anda perlu mengkonfigurasi keepalive di DUA tempat.

Pertama, untuk koneksi client:

keepalive_timeout 65;
keepalive_requests 100;

Ini memberitahu Nginx untuk menjaga koneksi client tetap terbuka selama 65 detik dan mengizinkan hingga 100 request per koneksi.

Tapi kebanyakan developer lupa bagian kedua: upstream keepalive.

upstream backend {
    server 127.0.0.1:3000;
    keepalive 32;
}

location / {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
}

Tanpa ini, Nginx membuka koneksi baru ke backend Anda untuk setiap request tunggal. Backend Anda menghabiskan lebih banyak waktu membuat koneksi daripada memproses request.

Kompresi Gzip: Kemenangan Performa Gratis

Mengaktifkan kompresi gzip bisa mengurangi ukuran response hingga 70% atau lebih.

Namun banyak server production berjalan tanpanya karena tidak diaktifkan secara default di beberapa distribusi.

gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript 
           application/json application/javascript application/xml+rss 
           application/rss+xml font/truetype font/opentype 
           application/vnd.ms-fontobject image/svg+xml;

gzip_comp_level adalah trade-off antara penggunaan CPU dan rasio kompresi. Level 6 adalah sweet spot untuk sebagian besar kasus.

Melebihi level 6 memberikan hasil yang semakin berkurang sambil meningkatkan beban CPU secara signifikan.

Caching: Opsi Nuklir

Jika backend Anda menghasilkan response yang sama berulang kali, Anda membuang resource.

Nginx bisa meng-cache response dan menyajikannya langsung tanpa menyentuh backend Anda.

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m 
                 max_size=1g inactive=60m use_temp_path=off;

location / {
    proxy_cache my_cache;
    proxy_cache_valid 200 60m;
    proxy_cache_valid 404 10m;
    proxy_cache_use_stale error timeout http_500 http_502 http_503;
    proxy_cache_background_update on;
    proxy_cache_lock on;
    
    proxy_pass http://backend;
}

Direktif proxy_cache_use_stale sangat cerdas. Jika backend Anda down, Nginx menyajikan konten cache yang basi daripada menampilkan error.

User Anda melihat konten yang sedikit usang daripada 502 Bad Gateway.

Optimasi File Statis

Jika Nginx menyajikan file statis, pengaturan ini secara dramatis meningkatkan performa:

location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    access_log off;
    
    open_file_cache max=1000 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;
}

open_file_cache menjaga file descriptor di memori. Nginx tidak perlu mengakses filesystem untuk setiap request.

Menonaktifkan access_log untuk aset statis menghapus disk I/O yang tidak perlu.

SSL/TLS: Kecepatan Tanpa Mengorbankan Keamanan

Handshake SSL/TLS secara komputasional mahal.

Tapi Anda bisa mengoptimalkannya tanpa mengorbankan keamanan:

ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;

ssl_stapling on;
ssl_stapling_verify on;

Session cache memungkinkan client untuk melanjutkan sesi SSL sebelumnya tanpa handshake penuh.

Ini saja bisa mengurangi overhead SSL hingga 80% untuk pengunjung yang kembali.

Rate Limiting: Lindungi Backend Anda

Bahkan Nginx yang dikonfigurasi dengan sempurna tidak bisa menyelamatkan Anda dari lonjakan trafik yang membanjiri backend Anda.

Rate limiting melindungi infrastruktur Anda:

limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

location /api {
    limit_req zone=api_limit burst=20 nodelay;
    proxy_pass http://backend;
}

Ini mengizinkan 10 request per detik per IP, dengan burst allowance 20 request.

User yang sah tidak pernah menyadarinya. Penyerang dan script yang berperilaku buruk diblokir sebelum bisa menyebabkan kerusakan.

Monitoring: Ketahui Apa yang Sebenarnya Terjadi

Anda tidak bisa mengoptimalkan apa yang tidak bisa Anda ukur.

Aktifkan modul stub_status untuk melihat metrik real-time:

location /nginx_status {
    stub_status;
    allow 127.0.0.1;
    deny all;
}

Ini menampilkan koneksi aktif, request per detik, dan status koneksi.

Padukan ini dengan format access log untuk melacak waktu response:

log_format detailed '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    'rt=$request_time uct="$upstream_connect_time" '
                    'uht="$upstream_header_time" urt="$upstream_response_time"';

access_log /var/log/nginx/access.log detailed;

Sekarang Anda bisa melihat dengan tepat dimana waktu dihabiskan: di Nginx, dalam transit jaringan, atau di backend Anda.

Tips Praktis untuk Production

Tes perubahan konfigurasi sebelum deploy. Gunakan nginx -t untuk memvalidasi sintaks. Typo bisa menjatuhkan seluruh site Anda.

Reload secara graceful. Gunakan nginx -s reload daripada restart. Ini memungkinkan koneksi yang ada untuk selesai sebelum menerapkan perubahan.

Monitor limit file descriptor. Dengan worker_connections yang tinggi, Anda mungkin mencapai limit OS. Cek dengan ulimit -n dan tingkatkan jika diperlukan.

Gunakan access log terpisah untuk aplikasi berbeda. Ini membuat debugging dan analisis jauh lebih mudah saat terjadi masalah.

Jaga definisi upstream tetap terorganisir. Seiring infrastruktur Anda berkembang, pertahankan konvensi penamaan yang jelas untuk blok upstream.

Dokumentasikan perubahan Anda. Anda di masa depan (dan tim Anda) akan berterima kasih saat troubleshooting masalah performa jam 2 pagi.

Mulai konservatif, lalu optimalkan. Jangan melempar setiap optimasi ke config Anda sekaligus. Buat perubahan bertahap dan ukur dampaknya.

Perbedaan antara konfigurasi Nginx default dan yang dioptimalkan bisa mencapai 10x dalam throughput dan waktu response.

Berhenti memperlakukan web server Anda sebagai kotak hitam. Memahami fundamental ini mengubah Nginx dari reverse proxy misterius menjadi alat performa powerful yang benar-benar Anda kontrol.

#Nginx #Performance #DevOps #Backend #Web Server