insights/content/tutorials/my-nginx-kickstart-boilerplate/index.id.md

20 KiB

title description date lastmod draft noindex nav_weight series categories tags images authors
My Nginx Setup Kickstart / Boilerplate Settingan wajib saya untuk Nginx sebagai web server, reverse proxy; termasuk VTS module, analisis, dan logging. 2024-04-25T00:00:09+07:00 false false 1000
SysAdmin
Snippets
Nginx
ditatompel

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.

{{< bs/alert info >}} {{< bs/alert-heading "INFO:" >}} Saya memiliki open-source project {{< bs/alert-link "nginx-kickstart" "https://github.com/ditatompel/nginx-kickstart" >}} (boilerplate) untuk mempermudah menginstall Nginx dari repositori officialnya dan mengkompile Nginx VTS module di FRESH Debian 12 atau Ubuntu 22.04 server. {{< /bs/alert >}}

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:

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

Untuk Ubuntu:

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

Lalu import official signing key-nya Nginx:

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

Tambahkan Nginx stable package ke apt source list repositori kita:

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

Prioritaskan official Nginx package:

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

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

sudo 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:

load_module modules/ngx_http_geoip_module.so;
load_module modules/ngx_stream_geoip_module.so;

event {
    worker_connections 65535; # Nginx default: 1024
}

http {
    geoip_country /usr/share/GeoIP/GeoIP.dat;

    # ...
}

Mempersiapkan struktur direktori Nginx

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

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

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

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

Buat DH-param dengan menjalankan perintah:

sudo 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:

#!/usr/bin/env bash
# Nginx setup for cloudflare's IPs.
# https://github.com/ditatompel/nginx-kickstart/blob/main/etc/nginx/cloudflare-ips.sh
# This is modified version of itsjfx's cloudflare-nginx-ips
# Ref of original script:
# https://github.com/itsjfx/cloudflare-nginx-ips/blob/master/cloudflare-ips.sh

set -e

[ "$(id -u)" -ne 0 ] && echo "This script must be run as root" && exit 1

CF_REAL_IPS_PATH=/etc/nginx/snippets/cloudflare_real_ips.conf
CF_WHITELIST_PATH=/etc/nginx/snippets/cloudflare_whitelist.conf
CF_GEOIP_PROXY_PATH=/etc/nginx/snippets/cloudflare_geoip_proxy.conf

for file in $CF_REAL_IPS_PATH $CF_WHITELIST_PATH $CF_GEOIP_PROXY_PATH; do
    echo "# https://www.cloudflare.com/ips" > $file
    echo "# Generated at $(LC_ALL=C date)" >> $file
done

echo "geo \$realip_remote_addr \$cloudflare_ip {
    default 0;" >> $CF_WHITELIST_PATH

for type in v4 v6; do
    for ip in `curl -sL https://www.cloudflare.com/ips-$type`; do
        echo "set_real_ip_from $ip;" >> $CF_REAL_IPS_PATH;
        echo "    $ip 1;" >> $CF_WHITELIST_PATH;
        echo "geoip_proxy $ip;" >> $CF_GEOIP_PROXY_PATH;
    done
done

echo "}
# if your vhost is behind CloudFlare proxy and you want your site only
# accessible from Cloudflare proxy, add this in your server{} block:
# if (\$cloudflare_ip != 1) {
#    return 403;
# }" >> $CF_WHITELIST_PATH

nginx -t && systemctl reload nginx

# 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:

http {
    # ...

    # Cloudflare IPs
    ################
    include /etc/nginx/snippets/cloudflare_real_ips.conf;
    real_ip_header X-Forwarded-For; # atau CF-Connecting-IP jika menggunakan Cloudflare
    # cloudflare map
    include /etc/nginx/snippets/cloudflare_whitelist.conf;

    # ...

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:

http {
    # ...

    # VCOMBINED log format style
    log_format vcombined '$host:$server_port '
        '$remote_addr - $remote_user [$time_local] '
        '"$request" $status $body_bytes_sent '
        '"$http_referer" "$http_user_agent"';

    # ...

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):

http {
    # ...

    # JSON style log format
    log_format json_analytics escape=json '{'
        '"msec": "$msec", ' # request unixtime in seconds with a milliseconds resolution
        '"connection": "$connection", ' # connection serial number
        '"connection_requests": "$connection_requests", ' # number of requests made in connection
        '"pid": "$pid", ' # process pid
        '"request_id": "$request_id", ' # the unique request id
        '"request_length": "$request_length", ' # request length (including headers and body)
        '"remote_addr": "$remote_addr", ' # client IP
        '"remote_user": "$remote_user", ' # client HTTP username
        '"remote_port": "$remote_port", ' # client port
        '"time_local": "$time_local", '
        '"time_iso8601": "$time_iso8601", ' # local time in the ISO 8601 standard format
        '"request": "$request", ' # full path no arguments if the request
        '"request_uri": "$request_uri", ' # full path and arguments if the request
        '"args": "$args", ' # args
        '"status": "$status", ' # response status code
        '"body_bytes_sent": "$body_bytes_sent", ' # the number of body bytes exclude headers sent to a client
        '"bytes_sent": "$bytes_sent", ' # the number of bytes sent to a client
        '"http_referer": "$http_referer", ' # HTTP referer
        '"http_user_agent": "$http_user_agent", ' # user agent
        '"http_x_forwarded_for": "$http_x_forwarded_for", ' # http_x_forwarded_for
        '"http_host": "$http_host", ' # the request Host: header
        '"server_name": "$server_name", ' # the name of the vhost serving the request
        '"request_time": "$request_time", ' # request processing time in seconds with msec resolution
        '"upstream": "$upstream_addr", ' # upstream backend server for proxied requests
        '"upstream_connect_time": "$upstream_connect_time", ' # upstream handshake time incl. TLS
        '"upstream_header_time": "$upstream_header_time", ' # time spent receiving upstream headers
        '"upstream_response_time": "$upstream_response_time", ' # time spent receiving upstream body
        '"upstream_response_length": "$upstream_response_length", ' # upstream response length
        '"upstream_cache_status": "$upstream_cache_status", ' # cache HIT/MISS where applicable
        '"ssl_protocol": "$ssl_protocol", ' # TLS protocol
        '"ssl_cipher": "$ssl_cipher", ' # TLS cipher
        '"scheme": "$scheme", ' # http or https
        '"request_method": "$request_method", ' # request method
        '"server_protocol": "$server_protocol", ' # request protocol, like HTTP/1.1 or HTTP/2.0
        '"pipe": "$pipe", ' # "p" if request was pipelined, "." otherwise
        '"gzip_ratio": "$gzip_ratio", '
        '"geoip_country_code": "$geoip_country_code"'
        '}';

    # ...
}

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":

http {
    # ...
    
    map $request_uri$http_user_agent $is_loggable {
        ~*local          0;
        ~*Uptime-Kuma.*  0;
        default          1;
    }

    access_log     /var/log/nginx/access-vcombined.log vcombined if=$is_loggable;

    # ...
}

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:

http {
    # ...

    access_log     syslog:server=192.168.0.7:514,facility=local7,tag=nginx,severity=info vcombined if=$is_loggable;
    access_log     syslog:server=192.168.0.7:514,facility=local7,tag=nginx_grafana,severity=info json_analytics if=$is_loggable;

    # ...
}

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:

sudo 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:

nginx version: nginx/1.26.0
built by gcc 11.4.0 (Ubuntu 11.4.0-1ubuntu1~22.04)
built with OpenSSL 3.0.2 15 Mar 2022
TLS SNI support enabled
configure 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.

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

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

tar -xvzf nginx-1.26.0.tar.gz
cd 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:

git 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:

./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/:

make modules -j$(nproc)
sudo 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:

load_module modules/ngx_http_vhost_traffic_status_module.so;

Kemudian didalam http{} block, tambahkan konfigurasi berikut:

http {
    # ...

    geoip_country /usr/share/GeoIP/GeoIP.dat;
    vhost_traffic_status_zone;
    vhost_traffic_status_filter_by_set_key $geoip_country_code country::*;

    # ...
}

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

server {
    # ...

    # contoh konfigurasi untuk menampilkan halaman Nginx VTS status
    location /status {
        vhost_traffic_status_bypass_limit on;
        vhost_traffic_status_bypass_stats on;
        vhost_traffic_status_display;
        vhost_traffic_status_display_format html;
        access_log off;
        # contoh membatasi akses ke URI dari IP tertentu
        allow 127.0.0.1;
        allow 192.168.0.0/24;
        deny  all;
    }

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    # ...
}

Konfigurasi Akhir

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

Kredit dan Referensi