Access Log #

Setiap kali klien mengirimkan permintaan (request) HTTP ke server kita—baik itu berupa pengambilan halaman HTML, pengunggahan berkas, pemanggilan API, maupun pemuatan gambar kecil—Nginx akan memprosesnya dan secara otomatis mencatat detail kejadian tersebut ke dalam berkas Access Log.

Access log adalah salah satu instrumen terpenting bagi seorang administrator sistem (sysadmin) dan pengembang (developer). Melalui access log, kita dapat memantau volume lalu lintas (traffic), mendeteksi aktivitas mencurigakan (seperti pemindaian celah keamanan oleh bot), menganalisis perilaku pengguna, hingga mendiagnosis masalah performa aplikasi. Di artikel ini, kita akan membedah secara mendalam bagaimana Nginx menangani access log, anatomi format default, konfigurasi penyimpanan modular per virtual host, teknik optimasi memori menggunakan buffering, serta cara menyaring entri log secara dinamis dengan conditional logging.

Bagaimana Nginx Mencatat Access Log? #

Secara arsitektur, Nginx memproses koneksi masuk menggunakan model non-blocking event-driven. Ketika sebuah request selesai diproses dan respons dikirimkan kembali ke klien, Nginx tidak langsung menghentikan alur kerja begitu saja. Worker process Nginx akan segera merumuskan baris log yang sesuai dengan format yang telah ditentukan, lalu menulisnya ke file log yang ditargetkan di sistem penyimpanan lokal kita.

Penulisan log ini terjadi di akhir siklus request. Hal ini penting untuk dipahami karena variabel-variabel yang dicatat (seperti ukuran data yang dikirimkan atau waktu pemrosesan) hanya dapat diketahui secara pasti setelah seluruh data respons berhasil ditransmisikan.


Anatomi Format Log Default: combined #

Secara bawaan (default), Nginx menggunakan format bernama combined. Format ini merupakan replika dari standar industri yang dipopulerkan oleh Apache Web Server, sehingga hampir semua alat analisis log pihak ketiga (seperti GoAccess, AWStats, Logstash, atau Splunk) dapat langsung membaca dan menerjemahkannya tanpa konfigurasi parser tambahan.

Berikut adalah definisi format combined yang dideklarasikan di dalam blok konfigurasi global http Nginx:

http {
    log_format combined '$remote_addr - $remote_user [$time_local] '
                        '"$request" $status $body_bytes_sent '
                        '"$http_referer" "$http_user_agent"';
}

Membedah Variabel Format combined #

Mari kita bedah masing-masing variabel yang digunakan dalam format default tersebut:

  • $remote_addr: Menyimpan alamat IP klien asli yang melakukan koneksi langsung ke server. Jika klien berada di balik proxy tanpa konfigurasi tambahan, variabel ini akan berisi IP dari proxy tersebut.
  • $remote_user: Mencatat nama pengguna (username) jika halaman atau lokasi tersebut dilindungi oleh modul HTTP Basic Authentication. Jika tidak ada autentikasi, bagian ini akan diisi dengan karakter strip (-).
  • $time_local: Menunjukkan waktu lokal server saat entri log ditulis, menggunakan format standar Common Log Format (misalnya, 15/Jun/2026:13:18:13 +0700).
  • $request: Berisi baris permintaan HTTP asli secara utuh dari klien, mencakup metode HTTP (GET, POST, dll.), URI/path yang diminta, dan protokol yang digunakan (misalnya, "GET /index.html HTTP/1.1").
  • $status: Kode status respons HTTP yang dikembalikan oleh server kepada klien (seperti 200 untuk sukses, 404 untuk tidak ditemukan, atau 500 untuk kesalahan internal server).
  • $body_bytes_sent: Jumlah bita data yang dikirimkan ke klien sebagai isi respons (response body). Nilai ini tidak mencakup ukuran header HTTP. Nilai ini lebih akurat dibanding variabel $bytes_sent karena mencerminkan beban payload data sebenarnya yang diunduh klien.
  • $http_referer: Berisi URL halaman asal (referrer) tempat klien menemukan tautan menuju halaman kita saat ini. Sangat berguna untuk analisis asal trafik.
  • $http_user_agent: Berisi informasi mengenai aplikasi browser, sistem operasi, dan perangkat yang digunakan oleh klien untuk mengakses server kita.

Contoh Pembacaan Baris Log #

Misalkan kita melihat baris berikut pada file /var/log/nginx/access.log:

203.0.113.88 - budi [16/Jun/2026:13:20:00 +0700] "GET /api/v1/profile HTTP/2.0" 200 4096 "https://blog.unisbadri.com/home" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36..."

Dari baris di atas, kita dapat menyimpulkan informasi berikut:

  1. Permintaan dikirim dari alamat IP 203.0.113.88.
  2. Pengguna terautentikasi sebagai budi melalui Basic Auth.
  3. Permintaan diproses pada tanggal 16 Juni 2026 pukul 13:20:00 waktu lokal server (zona waktu GMT+7).
  4. Klien mengakses endpoint /api/v1/profile menggunakan metode GET dengan protokol HTTP/2.0.
  5. Server mengembalikan status 200 (OK).
  6. Ukuran payload profil yang dikirimkan adalah 4096 bita (4 KB).
  7. Pengguna datang dari halaman luar https://blog.unisbadri.com/home.
  8. Pengguna menggunakan browser Chrome pada sistem operasi macOS.

Mengonfigurasi Direktif access_log #

Direktif access_log digunakan untuk mengaktifkan, menonaktifkan, atau mengarahkan pencatatan log ke jalur file tertentu dengan menggunakan format yang diinginkan. Direktif ini sangat fleksibel karena dapat ditempatkan di beberapa level konteks: http, server, location, serta di dalam blok limit_except atau if.

Sintaksis Dasar #

access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]];

Berikut adalah beberapa skenario umum penerapan direktif access_log:

http {
    # 1. Mengaktifkan log global dengan format default 'combined'
    access_log /var/log/nginx/access.log;

    server {
        listen 80;
        server_name example.com;

        # 2. Mengarahkan log khusus untuk virtual host ini ke file terpisah
        access_log /var/log/nginx/example.com-access.log combined;
        
        # 3. Mematikan access log sepenuhnya untuk server block ini (Tidak disarankan di produksi)
        # access_log off;
    }
}

Menonaktifkan Log untuk Pengurangan I/O Disk #

Pada server dengan kapasitas penyimpanan terbatas atau beban penulisan disk yang tinggi, kita sering kali ingin menghindari pencatatan log untuk request yang dianggap kurang penting atau terjadi sangat sering. Skenario yang paling umum adalah mematikan log untuk pemuatan aset statis (seperti gambar, file CSS, dan file JavaScript) serta endpoint healthcheck load balancer.

Berikut adalah konfigurasi yang direkomendasikan untuk menonaktifkan log pada lokasi tertentu:

server {
    listen 80;
    server_name myapp.com;
    access_log /var/log/nginx/myapp-access.log;

    # Abaikan logging untuk request healthcheck dari Load Balancer / Kubernetes
    location /healthz {
        access_log off;
        default_type text/plain;
        return 200 'OK';
    }

    # Abaikan logging untuk aset statis untuk menghemat kapasitas disk
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|woff|woff2|svg)$ {
        access_log off;
        expires 30d;
        add_header Cache-Control "public, no-transform";
    }
}

Dengan mengonfigurasi access_log off; pada lokasi file statis, kita tidak hanya menghemat ruang disk, tetapi juga mengurangi beban operasi input/output (I/O bottlenecks) pada media penyimpanan server kita.


Mengisolasi Log per Virtual Host (Server Block) #

Di lingkungan produksi yang melayani beberapa nama domain (multi-tenant), menumpuk seluruh entri log ke dalam satu file utama /var/log/nginx/access.log adalah sebuah kesalahan fatal. Ketika salah satu situs mengalami kegagalan atau lonjakan trafik mencurigakan, kita akan kesulitan memisahkan data tanpa bantuan pemrosesan regex yang berat.

Oleh karena itu, kita harus selalu mengisolasi log untuk setiap virtual host ke dalam berkasnya masing-masing.

# Konfigurasi Virtual Host 1: blog.unisbadri.com
server {
    listen 443 ssl;
    server_name blog.unisbadri.com;

    ssl_certificate /etc/letsencrypt/live/blog.unisbadri.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/blog.unisbadri.com/privkey.pem;

    # Isolasi Access Log dan Error Log khusus untuk blog
    access_log /var/log/nginx/blog.unisbadri.com-access.log combined;
    error_log /var/log/nginx/blog.unisbadri.com-error.log warn;

    root /var/www/blog;
    index index.html;
}

# Konfigurasi Virtual Host 2: api.unisbadri.com
server {
    listen 443 ssl;
    server_name api.unisbadri.com;

    ssl_certificate /etc/letsencrypt/live/api.unisbadri.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.unisbadri.com/privkey.pem;

    # Isolasi Access Log dengan format kustom untuk API
    access_log /var/log/nginx/api.unisbadri.com-access.log combined;
    error_log /var/log/nginx/api.unisbadri.com-error.log error;

    location / {
        proxy_pass http://api_backend;
    }
}

Memisahkan log seperti ini mempermudah kita saat ingin memantau log secara real-time pada satu domain tertentu menggunakan perintah seperti tail -f /var/log/nginx/blog.unisbadri.com-access.log.


Buffering Log untuk Trafik Tinggi (High Performance Logging) #

Secara default, setiap kali Nginx menyelesaikan sebuah request, ia akan langsung melakukan penulisan (system call write) ke file log di disk. Pada server yang melayani ribuan request per detik, penulisan terus-menerus ke disk secara sinkron dapat menyebabkan antrean I/O disk yang panjang (disk I/O wait), yang pada akhirnya akan menghambat kinerja respons server secara keseluruhan.

Untuk mengatasi masalah ini, Nginx menyediakan fitur Memory Buffering. Dengan mengaktifkan buffer, Nginx akan menampung entri log terlebih dahulu di dalam memori RAM sebelum menulisnya secara kolektif ke disk.

Berikut adalah ilustrasi alur kerja buffering log Nginx:

flowchart LR
    Klien1("Request Klien 1") --> Nginx("Nginx Worker")
    Klien2("Request Klien 2") --> Nginx
    Klien3("Request Klien 3") --> Nginx
    Nginx --> Buffer{"Memory Buffer (32KB)"}
    Buffer -->|Belum Penuh & < 5 Detik| Buffer
    Buffer -->|Penuh ATAU Batas Waktu 5s Terlampaui| Disk[("Disk Storage (access.log)")]

    classDef default fill:#f9f9f9,stroke:#d1d5db,stroke-width:1px,color:#111827;
    classDef nodeStyle fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e3a8a;
    class Nginx,Buffer nodeStyle;

Konfigurasi Buffering #

Kita dapat mengaktifkan buffer dengan menambahkan parameter buffer pada direktif access_log. Kita juga sangat disarankan untuk menyertakan parameter flush untuk menjamin entri log tetap ditulis secara berkala meskipun kapasitas buffer belum terpenuhi sepenuhnya.

http {
    # Tentukan format log kustom atau default
    log_format combined '$remote_addr - $remote_user [$time_local] '
                        '"$request" $status $body_bytes_sent '
                        '"$http_referer" "$http_user_agent"';

    # Aktifkan buffer sebesar 32 Kilobyte
    # Jika buffer belum penuh, paksa tulis ke disk setelah 5 detik
    access_log /var/log/nginx/access.log combined buffer=32k flush=5s;
}
  • buffer=32k: Nginx akan menahan entri log di memori RAM dan baru akan menulisnya ke disk ketika data log yang terkumpul telah mencapai 32 Kilobyte.
  • flush=5s: Menjamin bahwa jika lalu lintas sedang sepi dan buffer 32KB belum penuh, Nginx akan memaksa penulisan seluruh data log di buffer ke disk setiap 5 detik. Ini mencegah data log tertinggal terlalu lama di memori yang dapat membuat pemantauan real-time menjadi tidak akurat.

Analisis Trade-Off Buffering #

Penerapan buffering log memiliki kelebihan dan kekurangan yang harus kita timbang berdasarkan kebutuhan sistem kita:

AspekTanpa Buffering (Default)Dengan Buffering (buffer=32k flush=5s)
Beban Disk I/OTinggi (Menulis ke disk untuk setiap request).Sangat Rendah (Menulis secara berkelompok/batch).
Performa ServerDapat menurun jika terjadi antrean disk write yang padat.Sangat optimal, meminimalkan system call write.
Real-time MonitoringSangat instan, log langsung terlihat di file detik itu juga.Ada jeda (delay) hingga 5 detik sebelum log muncul di disk.
Keamanan DataSangat aman, risiko kehilangan entri log minimal.Jika server mati mendadak (power failure), log di RAM akan hilang.

[!TIP] Untuk lingkungan produksi berskala menengah hingga besar, mengaktifkan buffer=32k flush=5s adalah keputusan yang sangat logis karena peningkatan efisiensi CPU dan I/O disk jauh lebih berharga dibandingkan potensi kehilangan log beberapa detik terakhir saat terjadi crash total pada hardware server.


Conditional Logging dengan Parameter if #

Terkadang, mematikan log dengan mencocokkan blok location secara statik tidaklah cukup. Kita mungkin membutuhkan logika yang lebih dinamis untuk memutuskan apakah suatu request layak dicatat ke dalam log atau tidak. Nginx mengakomodasi kebutuhan ini melalui parameter if pada direktif access_log.

Parameter if hanya menerima variabel yang nilainya dievaluasi oleh Nginx. Jika nilai variabel tersebut adalah string kosong ("") atau angka 0, request tidak akan dicatat ke log. Jika bernilai selain itu, request akan dicatat.

Kita biasanya mengombinasikan direktif map untuk mengatur nilai variabel keputusan ini secara dinamis berdasarkan variabel request Nginx lainnya.

Skenario 1: Hanya Mencatat Log Respons Error (Status 4xx dan 5xx) #

Jika kita hanya ingin memantau anomali lalu lintas (seperti halaman rusak atau kegagalan API) tanpa mencatat kesuksesan transaksi biasa, kita bisa membuat filter status kode:

http {
    # Petakan status kode respons HTTP ke variabel $log_only_errors
    map $status $log_only_errors {
        ~^[23]  0;  # Jika status diawali dengan 2 atau 3 (sukses/redirect), set ke 0 (jangan di-log)
        default 1;  # Selain itu (4xx, 5xx), set ke 1 (catat ke log)
    }

    server {
        listen 80;
        server_name static.example.com;

        # Gunakan parameter if untuk conditional logging
        access_log /var/log/nginx/error-traffic.log combined if=$log_only_errors;
    }
}

Skenario 2: Hanya Mencatat Request yang Lambat (Performa Slow-Log) #

Skenario ini sangat berguna untuk mendeteksi endpoint API kita yang membutuhkan waktu pemrosesan lama di sisi backend. Kita dapat mendeteksi request berdasarkan variabel $request_time (total waktu pemrosesan request dalam hitungan detik dengan presisi milidetik):

http {
    # Petakan request time ke variabel $is_slow_request
    map $request_time $is_slow_request {
        ~^[0]\.[0-8] 0; # Jika request time di bawah 0.9 detik, set ke 0 (abaikan)
        default      1; # Jika request time >= 0.9 detik (hampir 1 detik atau lebih), set ke 1 (catat)
    }

    log_format performance_log '$remote_addr - [$time_local] "$request" '
                               '$status rt=$request_time urt=$upstream_response_time';

    server {
        listen 80;
        server_name api.unisbadri.com;

        # Catat semua request ke file log utama
        access_log /var/log/nginx/api-access.log;

        # Catat request lambat ke file log khusus untuk kebutuhan optimasi tim developer
        access_log /var/log/nginx/api-slow.log performance_log if=$is_slow_request;
    }
}

Melalui metode ini, tim pengembang kita dapat langsung membuka berkas /var/log/nginx/api-slow.log untuk mengidentifikasi kueri database lambat atau masalah memori di aplikasi tanpa terganggu oleh jutaan baris log transaksi sukses yang cepat.


Analisis Praktis Access Log via CLI #

Setelah berhasil mengonfigurasi dan mengumpulkan data di access log, kita perlu tahu cara membaca dan mengekstrak informasi berharga dari berkas tersebut secara cepat langsung dari terminal. Kita dapat menggunakan kombinasi perintah utilitas teks bawaan Unix seperti awk, grep, sort, uniq, dan head.

Berikut adalah kumpulan perintah CLI yang sangat berguna untuk pemecahan masalah cepat di server:

1. Menemukan 10 Alamat IP Teraktif (Indikasi Potensi Serangan DDoS/Scraping) #

# Kolom pertama ($1) pada format combined adalah IP klien ($remote_addr)
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -n 10
  • Cara kerja: Perintah ini mengambil kolom pertama dari file log, mengurutkannya agar IP yang sama berkelompok, menghitung jumlah kemunculan unik per IP (uniq -c), lalu mengurutkannya secara terbalik berdasarkan jumlah hit terbanyak (sort -rn), dan menampilkan 10 baris teratas.

2. Menemukan 10 Endpoint/URI Paling Sering Diakses #

# Kolom ketujuh ($7) pada format combined biasanya berisi path request (URI)
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -n 10

3. Memantau Jumlah Kemunculan HTTP Status Code (Melihat Kesehatan Aplikasi) #

# Kolom kesembilan ($9) berisi HTTP status code ($status)
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn

Jika kita melihat jumlah status 500 atau 502 melonjak tinggi secara tiba-tiba, itu adalah indikator kuat bahwa backend aplikasi kita sedang mengalami kendala crash atau kehabisan koneksi database.

4. Melacak User Agent Teraktif (Mengidentifikasi Bot / Scraper) #

# Kolom ke-12 dan seterusnya berisi User Agent, kita gunakan penanda tanda kutip
awk -F'"' '{print $6}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -n 10

5. Memantau Log Secara Real-time Sambil Menyaring Error #

Jika kita sedang melakukan pemeliharaan (maintenance) dan ingin melihat request yang gagal saja secara langsung:

tail -f /var/log/nginx/access.log | grep --line-buffered -E " 4[0-9]{2} | 5[0-9]{2} "

Ringkasan dan Praktik Terbaik #

  • Pisahkan Log per Domain: Selalu deklarasikan direktif access_log di setiap blok server virtual host agar log tidak tercampur menjadi satu file besar yang membingungkan.
  • Gunakan Buffering pada Produksi: Terapkan parameter buffer=32k flush=5s untuk mengurangi beban penulisan I/O disk pada server yang memiliki trafik tinggi.
  • Matikan Log yang Tidak Perlu: Manfaatkan access_log off; di dalam blok location untuk file aset statis dan endpoint healthcheck guna meminimalkan spam log.
  • Gunakan Conditional Logging: Gunakan parameter if= dikombinasikan dengan direktif map untuk memfilter data secara dinamis, seperti mengisolasi pencatatan request lambat atau request error saja.
  • Amankan Izin Akses Berkas: Pastikan berkas log Nginx hanya dapat dibaca oleh pengguna administratif. Secara default, direktori log biasanya diberi izin 0750 dengan kepemilikan grup adm atau www-data.

← Sebelumnya: Security Headers   Berikutnya: Error Log →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact