My Nginx Setup Kickstart / Boilerplate

Settingan wajib saya untuk Nginx sebagai web server, reverse proxy; termasuk VTS module, analisis, dan logging.
On this page

Sejak pertama kali saya menggunakan Nginx di pertengahan tahun 2011 lalu, Nginx langsung menjadi web server favorit saya. Apache yang sebelumnya merupakan “standard” web server di sistem operasi Linux sedikit demi sedikit mulai saya tinggalkan.

Seiring berjalannya waktu, beberapa web server baru mulai bermunculan, seperti Caddy dan Traefik. Sebagai seorang system administrator, tentu saja saya pernah mencoba menggunakannya, meskipun hanya sampai batas di penggunaan projek pribadi.

Namun, hati saya sepertinya selalu kembali ke Nginx. Aplikasi, service, atau apapun itu yang bisa saya expose melalui Nginx, akan saya expose menggunakan Nginx. Mungkin karena saya sudah terlalu nyaman dengan konfigurasi dan pengalaman menyenangkan bersama Nginx. XD

My use case

Karena saya memiliki IPv4 yang sangat terbatas, saya banyak menggunakan Nginx sebagai reverse proxy untuk service-service yang tidak memiliki IP publik (VM dengan jaringan lokal / internal). Hal ini sangat membantu menghemat alokasi IP publik. Di kasus ini, saya banyak bermain dengan proxy_cache dan http upstream untuk mengimplementasikan load balancing ataupun failover.

Ketika saya masih sering membuat program menggunakan PHP, saya menggunakan Nginx dan PHP-FPM tanpa adanya Apache (.htaccess) dibelakangnya. Jadi saya sering bermain dengan Nginx rewrite dan fastcgi_cache. Saat saya mulai membuat aplikasi menggunakan Rust dan Go, Nginx selalu bertugas sebagai reverse proxy sekaligus melakukan SSL termination.

Selain HTTP reverse proxy, saya kadang menggunakan module Nginx stream untuk TCP, UDP, bahkan Unix socket data stream.

Mengenai monitoring traffic, saya selalu menggunakan Nginx VTS module. Sudah tersedia nginx-vts-exporter untuk [Prometheus](https://prometheus .io/) yang sangat mudah dioperasikan untuk memproses data dari Nginx VTS module. Sedangkan untuk logging, beberapa log untuk virtual host yang saya nilai krusial dikirimkan secara real-time ke remote syslog server.

Sempurna sudah, semua fitur yang saya butuhkan terpenuhi oleh Nginx. Dan saatnya saya mulai mendokumentasikan proses instalasi dan konfigurasi untuk memenuhi apa yang saya butuhkan diatas.

Installasi Nginx (Official Repo)

Dokumentasi ini dibuat untuk Debian 12 dan Ubuntu 22.04, dan saya menggunakan official repositori dari Nginx, bukan repositori bawaan dari distro.

Pertama dan utama, selalu pastikan sistem dalam keadaan up-to-date dengan menjalankan perintah sudo aptget update && sudo apt-get dist-upgrade. Kemudian install package-package yang dibutuhkan untuk installasi Nginx.

Untuk Debian:

1apt install sudo curl gnupg2 ca-certificates lsb-release debian-archive-keyring

Untuk Ubuntu:

1apt install sudo curl gnupg2 ca-certificates lsb-release ubuntu-keyring

Lalu import official signing key-nya Nginx:

1curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \
2    | sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null

Tambahkan Nginx stable package ke apt source list repositori kita:

1echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \
2http://nginx.org/packages/ubuntu `lsb_release -cs` nginx" \
3    | sudo tee /etc/apt/sources.list.d/nginx.list

Prioritaskan official Nginx package:

1echo -e "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" \
2    | sudo tee /etc/apt/preferences.d/99nginx

Kemudian, install nginx dan nginx-module-geoip dengan menjalankan perintah:

1sudo apt update && sudo apt install nginx nginx-module-geoip

Load http_geoip_module dan stream_geoip_module, letakan load_module diatas event{} block dan geoip_country didalam http{} block:

 1load_module modules/ngx_http_geoip_module.so;
 2load_module modules/ngx_stream_geoip_module.so;
 3
 4event {
 5    worker_connections 65535; # Nginx default: 1024
 6}
 7
 8http {
 9    geoip_country /usr/share/GeoIP/GeoIP.dat;
10
11    # ...
12}

Mempersiapkan struktur direktori Nginx

Buat direktori sites-available, sites-enabled, certs, snippets di dalam direktori /etc/nginx dengan menjalankan perintah:

1sudo mkdir -p /etc/nginx/{sites-available,sites-enabled,certs,snippets}

Buat self-signed certificate (hanya digunakan sebagai konfigurasi awal yang nantinya digantikan oleh certbot):

1sudo openssl req -x509 -newkey rsa:4096 -days 365 -nodes \
2    -keyout /etc/nginx/certs/privkey.pem                 \
3    -out /etc/nginx/certs/fullchain.pem                  \
4    -subj '/CN=example.local/O=My Organization/C=US'

Buat DH-param dengan menjalankan perintah:

1sudo openssl dhparam -out /etc/nginx/certs/dhparam.pem 2048

Cloudflare IP Trusted Proxy

Jika ada virtual host yang berada dibalik Cloudflare reverse proxy, sangat disarankan untuk menambahkan IP Cloudflare ke trusted proxy di konfigurasi Nginx.

Buat executable shell script /etc/nginx/cloudflare-ips.sh berikut:

 1#!/usr/bin/env bash
 2# Nginx setup for cloudflare's IPs.
 3# https://github.com/ditatompel/nginx-kickstart/blob/main/etc/nginx/cloudflare-ips.sh
 4# This is modified version of itsjfx's cloudflare-nginx-ips
 5# Ref of original script:
 6# https://github.com/itsjfx/cloudflare-nginx-ips/blob/master/cloudflare-ips.sh
 7
 8set -e
 9
10[ "$(id -u)" -ne 0 ] && echo "This script must be run as root" && exit 1
11
12CF_REAL_IPS_PATH=/etc/nginx/snippets/cloudflare_real_ips.conf
13CF_WHITELIST_PATH=/etc/nginx/snippets/cloudflare_whitelist.conf
14CF_GEOIP_PROXY_PATH=/etc/nginx/snippets/cloudflare_geoip_proxy.conf
15
16for file in $CF_REAL_IPS_PATH $CF_WHITELIST_PATH $CF_GEOIP_PROXY_PATH; do
17    echo "# https://www.cloudflare.com/ips" > $file
18    echo "# Generated at $(LC_ALL=C date)" >> $file
19done
20
21echo "geo \$realip_remote_addr \$cloudflare_ip {
22    default 0;" >> $CF_WHITELIST_PATH
23
24for type in v4 v6; do
25    for ip in `curl -sL https://www.cloudflare.com/ips-$type`; do
26        echo "set_real_ip_from $ip;" >> $CF_REAL_IPS_PATH;
27        echo "    $ip 1;" >> $CF_WHITELIST_PATH;
28        echo "geoip_proxy $ip;" >> $CF_GEOIP_PROXY_PATH;
29    done
30done
31
32echo "}
33# if your vhost is behind CloudFlare proxy and you want your site only
34# accessible from Cloudflare proxy, add this in your server{} block:
35# if (\$cloudflare_ip != 1) {
36#    return 403;
37# }" >> $CF_WHITELIST_PATH
38
39nginx -t && systemctl reload nginx
40
41# vim: set ts=4 sw=4 et:

Shell script diatas akan mendownload list IP milik Cloudflare untuk diproses dan disimpan di /etc/nginx/snippets/cloudflare_*.conf. Silahkan buat cronjob untuk menjalankan script tersebut secara berkala (per minggu / per bulan).

Untuk konfigurasi Nginx-nya, tambahkan konfigurasi berikut ke dalam http{} block di /etc/nginx/nginx.conf:

 1http {
 2    # ...
 3
 4    # Cloudflare IPs
 5    ################
 6    include /etc/nginx/snippets/cloudflare_real_ips.conf;
 7    real_ip_header X-Forwarded-For; # atau CF-Connecting-IP jika menggunakan Cloudflare
 8    # cloudflare map
 9    include /etc/nginx/snippets/cloudflare_whitelist.conf;
10
11    # ...

Logging

Fitur logging dapat memperlambat kinerja server (terutama karena DISK I/O yang tinggi) di situs dengan traffic yang tinggi. Namun logging juga sangat penting untuk memonitoring dan menganalisa aktifitas server.

Log Format

Ada beberapa log format yang umum digunakan dan dapat diintegrasikan dengan aplikasi 3rd-party, misalnya format (V)COMMON atau (V)COMBINED.

VCOMBINED format

Tambahkan konfigurasi berikut ke dalam http{} block:

 1http {
 2    # ...
 3
 4    # VCOMBINED log format style
 5    log_format vcombined '$host:$server_port '
 6        '$remote_addr - $remote_user [$time_local] '
 7        '"$request" $status $body_bytes_sent '
 8        '"$http_referer" "$http_user_agent"';
 9
10    # ...

Saya biasanya menggunakan log format VCOMBINED yang kemudian saya integrasikan dengan GoAccess.

Custom JSON log

Untuk beberapa kasus, saya menggunakan Nginx integration di Grafana Cloud yang menggunakan custom access log format (JSON):

 1http {
 2    # ...
 3
 4    # JSON style log format
 5    log_format json_analytics escape=json '{'
 6        '"msec": "$msec", ' # request unixtime in seconds with a milliseconds resolution
 7        '"connection": "$connection", ' # connection serial number
 8        '"connection_requests": "$connection_requests", ' # number of requests made in connection
 9        '"pid": "$pid", ' # process pid
10        '"request_id": "$request_id", ' # the unique request id
11        '"request_length": "$request_length", ' # request length (including headers and body)
12        '"remote_addr": "$remote_addr", ' # client IP
13        '"remote_user": "$remote_user", ' # client HTTP username
14        '"remote_port": "$remote_port", ' # client port
15        '"time_local": "$time_local", '
16        '"time_iso8601": "$time_iso8601", ' # local time in the ISO 8601 standard format
17        '"request": "$request", ' # full path no arguments if the request
18        '"request_uri": "$request_uri", ' # full path and arguments if the request
19        '"args": "$args", ' # args
20        '"status": "$status", ' # response status code
21        '"body_bytes_sent": "$body_bytes_sent", ' # the number of body bytes exclude headers sent to a client
22        '"bytes_sent": "$bytes_sent", ' # the number of bytes sent to a client
23        '"http_referer": "$http_referer", ' # HTTP referer
24        '"http_user_agent": "$http_user_agent", ' # user agent
25        '"http_x_forwarded_for": "$http_x_forwarded_for", ' # http_x_forwarded_for
26        '"http_host": "$http_host", ' # the request Host: header
27        '"server_name": "$server_name", ' # the name of the vhost serving the request
28        '"request_time": "$request_time", ' # request processing time in seconds with msec resolution
29        '"upstream": "$upstream_addr", ' # upstream backend server for proxied requests
30        '"upstream_connect_time": "$upstream_connect_time", ' # upstream handshake time incl. TLS
31        '"upstream_header_time": "$upstream_header_time", ' # time spent receiving upstream headers
32        '"upstream_response_time": "$upstream_response_time", ' # time spent receiving upstream body
33        '"upstream_response_length": "$upstream_response_length", ' # upstream response length
34        '"upstream_cache_status": "$upstream_cache_status", ' # cache HIT/MISS where applicable
35        '"ssl_protocol": "$ssl_protocol", ' # TLS protocol
36        '"ssl_cipher": "$ssl_cipher", ' # TLS cipher
37        '"scheme": "$scheme", ' # http or https
38        '"request_method": "$request_method", ' # request method
39        '"server_protocol": "$server_protocol", ' # request protocol, like HTTP/1.1 or HTTP/2.0
40        '"pipe": "$pipe", ' # "p" if request was pipelined, "." otherwise
41        '"gzip_ratio": "$gzip_ratio", '
42        '"geoip_country_code": "$geoip_country_code"'
43        '}';
44
45    # ...
46}

Conditional (dynamic) logging

Dengan map, dan if keyword, kita dapat menentukan apa saya yang akan di-log dan apa yang tidak. Misalnya, saya tidak melakukan logging jika URI ada kata “local” atau User Agent mengandung kata “Uptime-Kuma”:

 1http {
 2    # ...
 3    
 4    map $request_uri$http_user_agent $is_loggable {
 5        ~*local          0;
 6        ~*Uptime-Kuma.*  0;
 7        default          1;
 8    }
 9
10    access_log     /var/log/nginx/access-vcombined.log vcombined if=$is_loggable;
11
12    # ...
13}

Remote Log UDP (rsyslog)

Bagi saya, sentraliasi log sangat mempermudah pekerjaan saya dalam melakukan analisa dan troubleshooting server.

Di Nginx, kita dapat dengan mudah mengirimkan log ke remote server secara real-time. Misalnya, kita dapat mengirimkan log ke remote rsyslog server (UDP) dengan contoh konfigurasi berikut:

1http {
2    # ...
3
4    access_log     syslog:server=192.168.0.7:514,facility=local7,tag=nginx,severity=info vcombined if=$is_loggable;
5    access_log     syslog:server=192.168.0.7:514,facility=local7,tag=nginx_grafana,severity=info json_analytics if=$is_loggable;
6
7    # ...
8}

Compile Nginx VTS Module

Nginx VTS module tidak tersedia di Official Nginx repositori, sehingga kita tidak dapat menginstallnya menggunakan apt. Untuk mengkompile VTS module memerlukan C compiler, git, libpcre, libssl, dan zlib. Install package yang dibutuhkan tersebut dengan menjalankan perintah:

1sudo apt install git build-essential libpcre3-dev zlib1g-dev libssl-dev

Ini adalah bagian yang sangat penting, jika ingin menggunakan dynamically linked module, opsi mengkompile module harus sama dengan Nginx binary file yang akan digunakan, begitu pula dengan versi Nginx yang digunakan. Untuk mengetahui informasi yang kita butuhkan tersebut, jalankan perintah nginx -V. Contoh output:

1nginx version: nginx/1.26.0
2built by gcc 11.4.0 (Ubuntu 11.4.0-1ubuntu1~22.04)
3built with OpenSSL 3.0.2 15 Mar 2022
4TLS SNI support enabled
5configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-http_v3_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-g -O2 -ffile-prefix-map=/data/builder/debuild/nginx-1.26.0/debian/debuild-base/nginx-1.26.0=. -flto=auto -ffat-lto-objects -flto=auto -ffat-lto-objects -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC' --with-ld-opt='-Wl,-Bsymbolic-functions -flto=auto -ffat-lto-objects -flto=auto -Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie'

Download Nginx source dengan versi yang sama persis dengan yang sedang kita gunakan, dalam contoh ini 1.26.0.

1curl -O https://nginx.org/download/nginx-1.26.0.tar.gz

Lalu extract arsip Nginx source code tersebut, kemudian masuk ke direktori didalamnya:

1tar -xvzf nginx-1.26.0.tar.gz
2cd nginx-1.26.0

Kemudian, clone repositori vozlt/nginx-module-vts dan gunakan rilis tag terakhir. Saat artikel ini dibuat, rilis tag terakhir adalah v0.2.2, maka:

1git clone -b v0.2.2 https://github.com/vozlt/nginx-module-vts.git

Configure dengan argumen yang sama dari output nginx -V diatas dan tambahkan --add-dynamic-module=./nginx-module-vts/. Contoh di artikel ini:

1./configure --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-http_v3_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-g -O2 -ffile-prefix-map=/data/builder/debuild/nginx-1.26.0/debian/debuild-base/nginx-1.26.0=. -flto=auto -ffat-lto-objects -flto=auto -ffat-lto-objects -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC' --with-ld-opt='-Wl,-Bsymbolic-functions -flto=auto -ffat-lto-objects -flto=auto -Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie' -add-dynamic-module=./nginx-module-vts/

Build, kemudian copy VTS module yang baru saja dicompile ke /etc/nginx/modules/:

1make modules -j$(nproc)
2sudo cp objs/ngx_http_vhost_traffic_status_module.so /etc/nginx/modules/

Konfigurasi Nginx VTS Module

Edit file /etc/nginx/nginx.conf dan load host_traffic_status_module berikut diatas event{} block:

1load_module modules/ngx_http_vhost_traffic_status_module.so;

Kemudian didalam http{} block, tambahkan konfigurasi berikut:

1http {
2    # ...
3
4    geoip_country /usr/share/GeoIP/GeoIP.dat;
5    vhost_traffic_status_zone;
6    vhost_traffic_status_filter_by_set_key $geoip_country_code country::*;
7
8    # ...
9}

Untuk menampilkan halaman VTS traffic status, tambahkan contoh konfigurasi berikut ke server{} block (misalnya di /etc/nginx/conf.d/default.conf):

 1server {
 2    # ...
 3
 4    # contoh konfigurasi untuk menampilkan halaman Nginx VTS status
 5    location /status {
 6        vhost_traffic_status_bypass_limit on;
 7        vhost_traffic_status_bypass_stats on;
 8        vhost_traffic_status_display;
 9        vhost_traffic_status_display_format html;
10        access_log off;
11        # contoh membatasi akses ke URI dari IP tertentu
12        allow 127.0.0.1;
13        allow 192.168.0.0/24;
14        deny  all;
15    }
16
17    location / {
18        root   /usr/share/nginx/html;
19        index  index.html index.htm;
20    }
21
22    # ...
23}

Konfigurasi Akhir

Sebagai referensi konfigurasi akhir, silahkan lihat di repositori https://github.com/ditatompel/nginx-kickstart/tree/main/etc/nginx.

Kredit dan Referensi