/static/codemoomoo2.png

9. Web Server and Reverse Proxy (Nginx, Caddy)

บทเรียนนี้ครอบคลุมแนวคิดของ Web Server, การติดตั้งและตั้งค่า Nginx, การทำ Reverse Proxy, Load Balancing, SSL/TLS, Caching รวมถึง Caddy ซึ่งเป็น Web Server ทางเลือกที่ตั้งค่าง่ายและรองรับ HTTPS อัตโนมัติ พร้อมตัวอย่างการใช้งานจริงที่นำไปประยุกต์ได้ทันที


9.1 แนวคิดของ Web Server และ HTTP

Web Server คือซอฟต์แวร์ที่ทำหน้าที่รับคำขอ (Request) จาก Client ผ่านโพรโทคอล HTTP/HTTPS และส่งทรัพยากร (Resource) ที่ร้องขอกลับไปในรูปของ Response ตัวอย่างที่นิยม ได้แก่ Nginx, Apache HTTP Server, Caddy, LiteSpeed และ IIS โดย Web Server สมัยใหม่ไม่ได้ทำหน้าที่เพียงเสิร์ฟไฟล์ Static เท่านั้น แต่ยังทำหน้าที่เป็น Reverse Proxy, Load Balancer, TLS Terminator และ API Gateway ด้วย

flowchart LR
    Client["🖥️ Client
เบราว์เซอร์"] -->|HTTP Request| WS["⚙️ Web Server
(Nginx/Caddy)"] WS -->|Static| FS[("📁 Filesystem
HTML/CSS/JS")] WS -->|Dynamic| App["🐍 Application Server
(Node.js/Python/PHP)"] App --> DB[("🗄️ Database")] WS -->|HTTP Response| Client style Client fill:#458588,stroke:#83a598,color:#ebdbb2 style WS fill:#d65d0e,stroke:#fe8019,color:#282828 style App fill:#98971a,stroke:#b8bb26,color:#282828 style DB fill:#b16286,stroke:#d3869b,color:#ebdbb2 style FS fill:#689d6a,stroke:#8ec07c,color:#282828

9.1.1 HTTP/HTTPS Protocol: 1.0, 1.1, 2, 3/QUIC

HTTP (HyperText Transfer Protocol) คือโพรโทคอลระดับ Application Layer ที่ใช้สื่อสารระหว่าง Client กับ Server โดยมีวิวัฒนาการตามช่วงเวลา ดังตารางต่อไปนี้

เวอร์ชัน ปี คุณสมบัติเด่น Transport
HTTP/0.9 1991 รองรับเฉพาะ GET, ตอบกลับ HTML เท่านั้น TCP
HTTP/1.0 1996 เพิ่ม Header, Status Code, Method TCP (1 connection/request)
HTTP/1.1 1997 Persistent Connection, Pipelining, Host header, Chunked transfer TCP (Keep-Alive)
HTTP/2 2015 Binary framing, Multiplexing, Header compression (HPACK), Server Push TCP + TLS
HTTP/3 2022 ใช้ QUIC แทน TCP, ลด head-of-line blocking, 0-RTT handshake UDP (QUIC)

ความแตกต่างสำคัญระหว่าง HTTP/1.1 กับ HTTP/2:

sequenceDiagram
    participant C as Client
    participant S as Server
    Note over C,S: HTTP/1.1 Persistent Connection
    C->>S: GET /index.html
    S-->>C: 200 OK + HTML
    C->>S: GET /style.css
    S-->>C: 200 OK + CSS
    Note over C,S: HTTP/2 Multiplexing
    C->>S: Stream 1: GET /index.html
    C->>S: Stream 3: GET /style.css
    C->>S: Stream 5: GET /app.js
    S-->>C: Stream 1, 3, 5 (parallel)

9.1.2 Request-Response Cycle

วงจรการสื่อสารพื้นฐาน (Request-Response Cycle) ของ HTTP ประกอบด้วยขั้นตอน

  1. DNS Resolution — Client แปลงโดเมนเป็น IP Address ผ่าน DNS Server
  2. TCP Handshake — Client เปิด TCP connection ด้วย 3-way handshake (SYN → SYN-ACK → ACK)
  3. TLS Handshake (กรณี HTTPS) — เจรจา Cipher Suite, แลกเปลี่ยน Key, ตรวจสอบ Certificate
  4. HTTP Request — Client ส่ง Method, URI, Header และ Body (ถ้ามี)
  5. Server Processing — Server ประมวลผลและสร้าง Response
  6. HTTP Response — Server ส่ง Status Code, Header และ Body กลับไป
  7. Connection Termination — ปิด connection หรือคงไว้ผ่าน Keep-Alive

โครงสร้างของ HTTP Request และ Response มีรูปแบบ

# HTTP Request
GET /api/users/42 HTTP/1.1
Host: api.example.com
User-Agent: Mozilla/5.0
Accept: application/json
Authorization: Bearer eyJhbGc...

# HTTP Response
HTTP/1.1 200 OK
Date: Sat, 25 Apr 2026 10:30:00 GMT
Content-Type: application/json
Content-Length: 87
Cache-Control: max-age=300

{"id":42,"name":"Moo","role":"lecturer","university":"RMUTSV"}

9.1.3 Method: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS

HTTP Method บ่งบอกการกระทำที่ต้องการให้ทำกับทรัพยากร (Resource) ในระบบ

Method ใช้ทำอะไร Idempotent Safe มี Body
GET ดึงข้อมูล ❌ (โดยทั่วไป)
POST สร้างข้อมูลใหม่ / ส่งข้อมูล
PUT แทนที่ข้อมูลทั้งก้อน
PATCH แก้ไขข้อมูลบางส่วน
DELETE ลบข้อมูล
HEAD ดึงเฉพาะ Header (ไม่มี Body)
OPTIONS ตรวจสอบความสามารถของ Server (CORS Preflight)

Idempotent = ส่งซ้ำกี่ครั้งก็ให้ผลเดียวกัน, Safe = ไม่ทำให้ State ของ Server เปลี่ยน

9.1.4 Status Code: 1xx, 2xx, 3xx, 4xx, 5xx

HTTP Status Code เป็นเลข 3 หลักที่บ่งบอกผลลัพธ์ของการประมวลผล แบ่งเป็น 5 กลุ่ม

HTTP Header คือคู่ Key-Value ที่ใช้แลกเปลี่ยน Metadata ระหว่าง Client กับ Server แบ่งเป็น

Cookie คือไฟล์เล็กๆ ที่ Server ส่งไปเก็บที่ Client ผ่าน Header Set-Cookie และ Client จะส่งกลับมาทุกครั้งที่ Request ผ่าน Header Cookie

Set-Cookie: session_id=abc123; Path=/; HttpOnly; Secure; SameSite=Strict; Max-Age=3600

Cookie Attribute สำคัญ:

Session คือสถานะของผู้ใช้ที่ Server เก็บไว้ โดยปกติจะใช้ Cookie เก็บ session_id เพื่ออ้างอิงข้อมูล Session ที่ฝั่ง Server (อาจเก็บใน Redis, Memcached, Database)

9.1.6 Static vs Dynamic Content

Static Content คือเนื้อหาที่ไม่เปลี่ยนแปลงตามผู้ใช้หรือเวลา เช่น HTML, CSS, JavaScript, รูปภาพ, ฟอนต์, วิดีโอ ไฟล์เหล่านี้สามารถเสิร์ฟได้รวดเร็วและแคชได้ง่าย

Dynamic Content คือเนื้อหาที่สร้างขึ้นตามคำขอ เช่น หน้าโปรไฟล์ผู้ใช้ ผลลัพธ์การค้นหา หรือข้อมูลแบบเรียลไทม์ ต้องอาศัย Application Server (เช่น PHP-FPM, Gunicorn, Node.js) ในการประมวลผล

คุณสมบัติ Static Dynamic
ความเร็ว เร็วมาก ช้ากว่า (ต้องประมวลผล)
Caching ง่าย (CDN, Browser) ยาก (ต้องระวัง)
ทรัพยากร ใช้น้อย ใช้มาก (CPU, Memory)
ตัวอย่าง landing page, blog (SSG) dashboard, e-commerce

9.1.7 MIME Type, Content-Type

MIME Type (Multipurpose Internet Mail Extensions) ใช้บอกประเภทของข้อมูลที่ส่งผ่าน HTTP โดยส่งใน Header Content-Type มีรูปแบบ type/subtype เช่น

Web Server จะกำหนด MIME Type ตามนามสกุลไฟล์โดยอัตโนมัติ ผ่านไฟล์ /etc/nginx/mime.types ใน Nginx

ตัวอย่างการระบุ MIME Type ใน Response

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 45

{"message":"สวัสดีครับ","status":"ok"}

9.2 การติดตั้ง Nginx

Nginx (อ่านว่า "Engine-X") เป็น Web Server แบบ Event-driven ที่พัฒนาโดย Igor Sysoev ในปี 2004 เพื่อแก้ปัญหา C10K Problem (รองรับ 10,000 connection พร้อมกัน) ปัจจุบันถือเป็น Web Server ที่นิยมใช้งานบน Server Linux เป็นอันดับต้นๆ ของโลก

9.2.1 การติดตั้งบน Debian/Ubuntu, Fedora/RHEL, Arch

บน Debian/Ubuntu ใช้ apt:

# อัพเดต package index
sudo apt update

# ติดตั้ง nginx จาก repository ทางการของ Ubuntu
sudo apt install -y nginx

# ตรวจสอบเวอร์ชัน
nginx -v

# เริ่มต้นและเปิดให้รันอัตโนมัติเมื่อ boot
sudo systemctl enable --now nginx

# ตรวจสอบสถานะ
sudo systemctl status nginx

บน Fedora/RHEL/Rocky/AlmaLinux ใช้ dnf:

# ติดตั้ง nginx
sudo dnf install -y nginx

# เปิด firewall ให้ port 80, 443
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

# เริ่มบริการ
sudo systemctl enable --now nginx

บน Arch Linux / Manjaro / EndeavourOS ใช้ pacman:

# ติดตั้งจาก official repository
sudo pacman -S nginx

# เริ่มบริการ
sudo systemctl enable --now nginx

ทดสอบการติดตั้ง เปิดเบราว์เซอร์ไปที่ http://localhost หากเห็นหน้า "Welcome to nginx!" แสดงว่าติดตั้งสำเร็จ

9.2.2 โครงสร้างไฟล์: /etc/nginx/nginx.conf, conf.d/, sites-available/, sites-enabled/

โครงสร้างไดเรกทอรีหลักของ Nginx (Debian/Ubuntu)

/etc/nginx/
├── nginx.conf              # ไฟล์ตั้งค่าหลัก
├── mime.types              # การ map นามสกุลไฟล์ → MIME type
├── fastcgi_params          # พารามิเตอร์สำหรับ FastCGI
├── proxy_params            # พารามิเตอร์สำหรับ proxy
├── conf.d/                 # ไฟล์ตั้งค่าเสริม (ถูก include โดย http block)
│   └── *.conf
├── sites-available/        # ไฟล์ตั้งค่า virtual host (เก็บไว้ที่นี่)
│   ├── default
│   └── example.com
├── sites-enabled/          # symlink ไปยัง sites-available ที่เปิดใช้งาน
│   └── default -> ../sites-available/default
├── snippets/               # snippet สำหรับ include ซ้ำๆ
│   ├── fastcgi-php.conf
│   └── ssl-params.conf
├── modules-available/
└── modules-enabled/

ในระบบ RHEL/Fedora จะไม่มี sites-available/ และ sites-enabled/ แต่จะใช้ conf.d/ เก็บไฟล์ตั้งค่าทั้งหมดแทน

9.2.3 การจัดการบริการ: systemctl start/enable/reload nginx

คำสั่งที่ใช้บ่อยในการจัดการบริการ Nginx ผ่าน systemd

# เริ่มบริการ
sudo systemctl start nginx

# หยุดบริการ
sudo systemctl stop nginx

# รีสตาร์ท (หยุดแล้วเริ่มใหม่ — connection ถูกตัด)
sudo systemctl restart nginx

# โหลด config ใหม่โดยไม่ตัด connection (graceful reload)
sudo systemctl reload nginx
# หรือ
sudo nginx -s reload

# เปิดใช้งานเมื่อ boot
sudo systemctl enable nginx

# ปิดการเปิดอัตโนมัติ
sudo systemctl disable nginx

# ตรวจสอบสถานะ
sudo systemctl status nginx

# ดู log แบบ real-time
sudo journalctl -u nginx -f

ความแตกต่างของ restart กับ reload

9.2.4 nginx -t (test config)

ก่อน reload ทุกครั้ง ควรทดสอบ syntax ของ config เพื่อป้องกัน Server ล่ม

# ทดสอบ config โดยไม่ต้อง restart
sudo nginx -t

# ทดสอบและแสดง config ทั้งหมดที่ถูก parse
sudo nginx -T

# ระบุ config file เอง
sudo nginx -t -c /etc/nginx/nginx.conf

ตัวอย่างผลลัพธ์ที่ถูกต้อง

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

ตัวอย่างเมื่อมี error

nginx: [emerg] unexpected "}" in /etc/nginx/sites-enabled/example.com:25
nginx: configuration file /etc/nginx/nginx.conf test failed

แนวปฏิบัติที่ดี (Best Practice): ใช้คำสั่งคู่ sudo nginx -t && sudo systemctl reload nginx เพื่อให้ reload เกิดขึ้นต่อเมื่อ config ถูกต้องเท่านั้น


9.3 Nginx Configuration

โครงสร้างของไฟล์ตั้งค่า Nginx ใช้ syntax แบบ block-based ที่ประกอบด้วย Directive และ Context (Block) โดย Directive แต่ละตัวต้องลงท้ายด้วย semicolon (;)

9.3.1 Context: main, events, http, server, location

Context คือขอบเขตที่ Directive มีผล แบ่งเป็นลำดับชั้นดังนี้

flowchart TB
    subgraph Main["main context (global)"]
        direction TB
        M1["user, worker_processes,
error_log, pid"] subgraph Events["events { }"] E1["worker_connections,
use, multi_accept"] end subgraph Http["http { }"] H1["mime.types, log_format,
gzip, ssl_protocols"] subgraph Server["server { } (virtual host)"] S1["listen, server_name,
root, index"] subgraph Location["location { }"] L1["try_files, proxy_pass,
return, rewrite"] end end end end style Main fill:#282828,stroke:#fabd2f,color:#ebdbb2 style Events fill:#3c3836,stroke:#83a598,color:#ebdbb2 style Http fill:#3c3836,stroke:#b8bb26,color:#ebdbb2 style Server fill:#504945,stroke:#fe8019,color:#ebdbb2 style Location fill:#665c54,stroke:#d3869b,color:#ebdbb2

ตัวอย่างไฟล์ /etc/nginx/nginx.conf ที่สมบูรณ์

# main context
user www-data;                     # user ที่ worker ใช้รัน
worker_processes auto;             # จำนวน worker = จำนวน CPU core
pid /run/nginx.pid;
error_log /var/log/nginx/error.log warn;

# events context — กำหนดพฤติกรรมการรับ connection
events {
    worker_connections 1024;       # จำนวน connection ต่อ worker
    use epoll;                     # I/O multiplexing บน Linux
    multi_accept on;
}

# http context — กำหนดพฤติกรรมของ HTTP server
http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # Log format
    log_format main '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';
    access_log /var/log/nginx/access.log main;

    # Performance
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;

    # Compression
    gzip on;
    gzip_types text/plain text/css application/json application/javascript;

    # รวมไฟล์ตั้งค่า virtual host
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

9.3.2 Directive พื้นฐาน: server_name, listen, root, index

Directive พื้นฐานที่ใช้ใน server block

# /etc/nginx/sites-available/example.com
server {
    listen 80;                                  # รับ HTTP บน port 80 ทุก IP
    listen [::]:80;                             # รับ IPv6 ด้วย
    # listen 443 ssl http2;                     # HTTPS + HTTP/2
    # listen 192.168.1.10:8080;                 # IP เฉพาะ + port 8080

    server_name example.com www.example.com;    # หลายโดเมนใน server block เดียว
    # server_name *.example.com;                # wildcard
    # server_name ~^(www\.)?(?<sub>.+)\.example\.com$;  # regex + named capture

    root /var/www/example.com/public;
    index index.html index.htm index.php;

    # location block จะอยู่ในส่วนถัดไป
}

9.3.3 location block และ Match Order (exact, prefix, regex)

location เป็น Directive ที่ใช้กำหนดพฤติกรรมเมื่อ URI ตรงกับ pattern โดย Nginx มี ลำดับการ match ที่ต้องเข้าใจให้ดี

Modifier ความหมาย ลำดับความสำคัญ
= Exact match 1 (สูงสุด)
^~ Prefix match (หยุดค้นหา regex ต่อ) 2
~ Regex match (case-sensitive) 3
~* Regex match (case-insensitive) 3
(ไม่มี) Prefix match (default) 4 (ต่ำสุด)

ขั้นตอนการ Match ของ Nginx

  1. ค้นหา exact match (=) ก่อน — ถ้าเจอใช้ทันที
  2. ค้นหา prefix match ที่ยาวที่สุดที่ตรงกัน — เก็บไว้ก่อน
  3. ถ้า prefix นั้นมี ^~ → ใช้ทันที (ข้าม regex)
  4. ค้นหา regex match ตามลำดับที่ปรากฏใน config — ถ้าเจอใช้ตัวแรกที่ match
  5. ถ้าไม่มี regex match → ใช้ prefix match จากข้อ 2
server {
    listen 80;
    server_name example.com;
    root /var/www/example.com;

    # 1. Exact match — ตรงเป๊ะกับ /
    location = / {
        try_files /index.html =404;
    }

    # 2. Prefix match พร้อม ^~ — match แล้วหยุดค้นหา regex
    location ^~ /static/ {
        expires 30d;
        access_log off;
    }

    # 3. Regex match (case-insensitive) — match รูปภาพ
    location ~* \.(jpg|jpeg|png|gif|webp|svg)$ {
        expires 7d;
        add_header Cache-Control "public, immutable";
    }

    # 4. Regex match — match PHP file
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    }

    # 5. Default prefix match — fallback สำหรับ URL อื่นๆ
    location / {
        try_files $uri $uri/ /index.html;
    }
}

9.3.4 try_files, return, rewrite

try_files — ตรวจสอบไฟล์ตามลำดับ ถ้ามีอยู่จะส่งไฟล์นั้น ถ้าไม่มีจะลองตัวถัดไป ปิดท้ายด้วย fallback (URI หรือ status code)

location / {
    # ลองหาไฟล์ → โฟลเดอร์ → fallback ไป /index.html (สำหรับ SPA)
    try_files $uri $uri/ /index.html;
}

location / {
    # หาไฟล์ ถ้าไม่เจอตอบ 404
    try_files $uri =404;
}

return — ตอบกลับทันทีโดยไม่ประมวลผลต่อ ใช้สำหรับ redirect หรือส่ง response สั้นๆ

# Redirect 301 ทั้งโดเมน
server {
    listen 80;
    server_name old.example.com;
    return 301 https://new.example.com$request_uri;
}

# ตอบ 403 พร้อมข้อความ
location /admin {
    return 403 "Access Denied";
}

# ตอบ JSON สั้นๆ
location /health {
    default_type application/json;
    return 200 '{"status":"ok"}';
}

rewrite — แก้ไข URI ภายใน (อาจ match ใหม่หรือ redirect ภายนอก)

# แก้ไข URI โดยไม่ redirect (last)
rewrite ^/old-path/(.*)$ /new-path/$1 last;

# Redirect 302 ภายนอก (ผู้ใช้เห็น URL ใหม่)
rewrite ^/blog/(.*)$ /articles/$1 redirect;

# Redirect 301 (permanent)
rewrite ^/blog/(.*)$ /articles/$1 permanent;

# break — ใช้ URI ใหม่ในต่อไปใน location เดิม
rewrite ^/api/v1/(.*)$ /v1/$1 break;

คำแนะนำ: หลีกเลี่ยง rewrite ถ้าใช้ return หรือ try_files แทนได้ เพราะเร็วกว่าและอ่านเข้าใจง่ายกว่า

9.3.5 include directive

include ใช้รวมไฟล์ config อื่นๆ เข้ามา ทำให้แยก config เป็นไฟล์ย่อยๆ ได้

http {
    # รวมไฟล์ทั้งหมดในโฟลเดอร์ conf.d
    include /etc/nginx/conf.d/*.conf;

    # รวม snippet ที่ใช้ซ้ำ
    include /etc/nginx/snippets/ssl-params.conf;
}

# ตัวอย่าง snippet ssl-params.conf
# /etc/nginx/snippets/ssl-params.conf
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512';
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;

9.3.6 Access Log และ Error Log (log_format)

Nginx มี log 2 ประเภทหลัก

การกำหนด log format

http {
    # log format ที่กำหนดเอง
    log_format combined_real '$remote_addr - $remote_user [$time_local] '
                              '"$request" $status $body_bytes_sent '
                              '"$http_referer" "$http_user_agent" '
                              '"$http_x_forwarded_for" rt=$request_time '
                              'uct="$upstream_connect_time" '
                              'urt="$upstream_response_time"';

    # log แบบ JSON สำหรับส่งเข้า ELK / Loki
    log_format json_log escape=json '{'
        '"time":"$time_iso8601",'
        '"remote_addr":"$remote_addr",'
        '"method":"$request_method",'
        '"uri":"$request_uri",'
        '"status":$status,'
        '"size":$body_bytes_sent,'
        '"referer":"$http_referer",'
        '"ua":"$http_user_agent",'
        '"rt":$request_time'
    '}';

    access_log /var/log/nginx/access.log combined_real;
    error_log  /var/log/nginx/error.log warn;
}

# ระดับ error_log: debug, info, notice, warn, error, crit, alert, emerg

ปิด log สำหรับ static asset เพื่อลดภาระดิสก์

location ~* \.(jpg|png|css|js|ico)$ {
    access_log off;
    expires 30d;
}

9.4 Virtual Host (Server Block)

Virtual Host (ใน Nginx เรียกว่า Server Block) คือกลไกที่ทำให้ Nginx ตัวเดียวสามารถโฮสต์เว็บไซต์ได้หลายเว็บบน Server เครื่องเดียว

9.4.1 การโฮสต์หลายเว็บไซต์บน IP เดียว (Name-based)

Name-based Virtual Hosting เป็นวิธีที่นิยมที่สุด โดยอาศัย Host header ที่ Browser ส่งมาเพื่อแยกแยะว่า request นี้ต้องการเข้าเว็บไหน

# /etc/nginx/sites-available/site-a.com
server {
    listen 80;
    server_name site-a.com www.site-a.com;
    root /var/www/site-a;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

# /etc/nginx/sites-available/site-b.com
server {
    listen 80;
    server_name site-b.com www.site-b.com;
    root /var/www/site-b;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

ขั้นตอนการเปิดใช้งาน virtual host (Debian/Ubuntu)

# สร้างโฟลเดอร์เว็บ
sudo mkdir -p /var/www/site-a /var/www/site-b
echo "<h1>Welcome to Site A</h1>" | sudo tee /var/www/site-a/index.html
echo "<h1>Welcome to Site B</h1>" | sudo tee /var/www/site-b/index.html

# เปิดใช้งาน virtual host ด้วย symlink
sudo ln -s /etc/nginx/sites-available/site-a.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/site-b.com /etc/nginx/sites-enabled/

# ทดสอบและ reload
sudo nginx -t && sudo systemctl reload nginx

# ทดสอบจาก client (ถ้าโดเมนยังไม่ผูก ให้แก้ /etc/hosts)
curl -H "Host: site-a.com" http://192.168.1.100
curl -H "Host: site-b.com" http://192.168.1.100

9.4.2 Port-based Virtual Host

Port-based Virtual Hosting ใช้ port ที่ต่างกันแยกแต่ละเว็บ — เหมาะกับสภาพแวดล้อม dev หรือ admin panel

server {
    listen 8080;
    server_name _;
    root /var/www/dev;
    index index.html;
}

server {
    listen 9090;
    server_name _;
    root /var/www/admin;
    index index.html;

    # จำกัดเฉพาะ IP ภายใน
    allow 192.168.1.0/24;
    deny all;
}

9.4.3 Default Server

เมื่อมี request เข้ามาที่ IP ของ Server แต่ Host header ไม่ตรงกับ server_name ใดๆ Nginx จะใช้ Default Server (server block แรกที่ listen port นั้น หรือที่กำหนด default_server)

# Default server — ตอบ 444 (close connection ทันที) สำหรับ host ที่ไม่รู้จัก
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    return 444;
}

# Server จริงสำหรับ example.com
server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/example.com;
}

เทคนิค Security: การใช้ return 444 ใน default server ช่วยปิด attack ที่ใช้ IP ตรงๆ หรือโดเมนที่ไม่ได้รับอนุญาต

9.4.4 Wildcard Hostname

รองรับ subdomain แบบไดนามิกด้วย wildcard หรือ regex

# Wildcard — match ทุก subdomain ของ example.com
server {
    listen 80;
    server_name *.example.com;
    root /var/www/wildcard;
}

# Regex + Named capture — สร้าง path ตาม subdomain
server {
    listen 80;
    server_name ~^(?<sub>.+)\.example\.com$;
    root /var/www/users/$sub;

    # /var/www/users/moo/index.html → moo.example.com
    # /var/www/users/john/index.html → john.example.com
}

9.5 Reverse Proxy ด้วย Nginx

9.5.1 แนวคิด Reverse Proxy vs Forward Proxy

flowchart LR
    subgraph Forward["🔵 Forward Proxy (ตัวแทนของ Client)"]
        direction LR
        C1["Client A"] --> FP["Forward Proxy
(Squid, Tor)"] C2["Client B"] --> FP FP --> Internet["🌐 Internet"] end subgraph Reverse["🟠 Reverse Proxy (ตัวแทนของ Server)"] direction LR Inet["🌐 Internet
Clients"] --> RP["Reverse Proxy
(Nginx, Caddy)"] RP --> B1["Backend 1"] RP --> B2["Backend 2"] RP --> B3["Backend 3"] end style Forward fill:#282828,stroke:#83a598,color:#ebdbb2 style Reverse fill:#282828,stroke:#fe8019,color:#ebdbb2 style FP fill:#458588,stroke:#83a598,color:#ebdbb2 style RP fill:#d65d0e,stroke:#fe8019,color:#282828 style B1 fill:#98971a,stroke:#b8bb26,color:#282828 style B2 fill:#98971a,stroke:#b8bb26,color:#282828 style B3 fill:#98971a,stroke:#b8bb26,color:#282828
คุณสมบัติ Forward Proxy Reverse Proxy
ตัวแทนของ Client Server
ตำแหน่ง ฝั่ง Client ฝั่ง Server
รู้จักโดย Client (ตั้งค่าเอง) Client (ใช้ผ่าน DNS)
Use Case Bypass firewall, Anonymity, Caching Load balancing, SSL termination, Security
ตัวอย่าง Squid, Tor, 3proxy Nginx, Caddy, HAProxy, Traefik

9.5.2 proxy_pass

proxy_pass เป็น Directive หลักของการทำ Reverse Proxy

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

    location / {
        proxy_pass http://127.0.0.1:3000;        # ส่งต่อไปยัง Node.js
    }
}

ความแตกต่างของ proxy_pass ที่มี / และไม่มี / ท้าย URL

# กรณี 1: ไม่มี / ท้าย — URI ถูกส่งต่อไปทั้งหมด
location /api/ {
    proxy_pass http://backend;
    # client → /api/users → backend → /api/users
}

# กรณี 2: มี / ท้าย — ตัด prefix ออก
location /api/ {
    proxy_pass http://backend/;
    # client → /api/users → backend → /users
}

# กรณี 3: มี path ท้าย
location /api/ {
    proxy_pass http://backend/v1/;
    # client → /api/users → backend → /v1/users
}

9.5.3 proxy_set_header: Host, X-Real-IP, X-Forwarded-For, X-Forwarded-Proto

เมื่อทำ Reverse Proxy ข้อมูลของ Client (เช่น IP จริง, scheme) จะหายไปจากมุมมองของ Backend ต้องส่งผ่าน Header เพิ่มเติม

location / {
    proxy_pass http://127.0.0.1:3000;

    # ส่งชื่อ host เดิมที่ client ขอมา (จำเป็นสำหรับ virtual host ที่ backend)
    proxy_set_header Host $host;

    # IP จริงของ client
    proxy_set_header X-Real-IP $remote_addr;

    # IP chain (รวม proxy ทั้งหมดที่ผ่านมา)
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    # บอก backend ว่า client ใช้ http หรือ https
    proxy_set_header X-Forwarded-Proto $scheme;

    # ส่ง host:port เดิม
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Port $server_port;
}

หมายเหตุ: X-Forwarded-* เป็น de-facto header ปัจจุบันมี standard คือ Forwarded (RFC 7239) แต่ยังไม่ใช้แพร่หลาย

9.5.4 WebSocket Proxy (Upgrade, Connection)

WebSocket ต้องการ HTTP Upgrade เพื่อเปลี่ยน Connection จาก HTTP เป็น WebSocket — ต้องตั้งค่าเพิ่ม

# กำหนด map เพื่อตอบ Connection: upgrade เฉพาะเมื่อ client ส่ง upgrade
http {
    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
    }
}

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

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;

        # WebSocket headers
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        # Timeout ที่ยาวพอสำหรับ long-lived connection
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

9.5.5 การเชื่อมต่อ Backend: Node.js, Python (uWSGI/Gunicorn), PHP-FPM

ตัวอย่างที่ 1: Node.js Application

upstream nodejs_app {
    server 127.0.0.1:3000;
    keepalive 32;          # รักษา connection ไว้ใช้ซ้ำ
}

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

    # เสิร์ฟ static asset โดยตรง (เร็วกว่าผ่าน Node)
    location /static/ {
        alias /var/www/app/dist/static/;
        expires 30d;
    }

    # ส่ง request อื่นๆ ไป Node
    location / {
        proxy_pass http://nodejs_app;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

ตัวอย่างที่ 2: Python (Gunicorn) — ผ่าน Unix Socket

upstream django_app {
    # Unix socket เร็วกว่า TCP สำหรับ same-host
    server unix:/run/gunicorn/django.sock fail_timeout=0;
}

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

    client_max_body_size 50M;     # รองรับ upload ใหญ่

    location /static/ {
        alias /var/www/django/static/;
        expires 7d;
    }

    location /media/ {
        alias /var/www/django/media/;
    }

    location / {
        proxy_pass http://django_app;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Timeout
        proxy_connect_timeout 5s;
        proxy_read_timeout 60s;
    }
}

ตัวอย่างที่ 3: PHP-FPM

server {
    listen 80;
    server_name php.example.com;
    root /var/www/php-app;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

    # ห้ามเข้าถึงไฟล์สำคัญ
    location ~ /\.(ht|env) {
        deny all;
    }
}

9.5.6 Buffering และ Timeout

Proxy Buffering ช่วยให้ Nginx อ่าน response จาก backend จนเสร็จก่อนส่งให้ client — เหมาะกับ backend ที่ช้า แต่อาจมีปัญหากับ streaming

location / {
    proxy_pass http://backend;

    # Buffering
    proxy_buffering on;            # เปิด buffer (default)
    proxy_buffer_size 4k;          # buffer สำหรับ response header
    proxy_buffers 8 16k;           # buffer สำหรับ body (8 buffers x 16KB)
    proxy_busy_buffers_size 32k;

    # Timeout
    proxy_connect_timeout 5s;      # timeout การเชื่อมต่อ backend
    proxy_send_timeout 60s;        # timeout การส่ง request ไป backend
    proxy_read_timeout 60s;        # timeout การรอ response

    # ปิด buffering สำหรับ streaming endpoint (เช่น Server-Sent Events)
    # proxy_buffering off;
    # proxy_cache off;
}

9.6 Load Balancing ด้วย Nginx

Load Balancing คือการกระจาย request ไปยัง Backend หลายตัวเพื่อ

  1. รองรับโหลดที่สูงขึ้น (Scalability)
  2. เพิ่มความพร้อมใช้งาน (High Availability)
  3. ลด Single Point of Failure

9.6.1 upstream block

http {
    upstream app_backend {
        server 192.168.1.10:8080;
        server 192.168.1.11:8080;
        server 192.168.1.12:8080;
    }

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

        location / {
            proxy_pass http://app_backend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
    }
}

9.6.2 Algorithm: Round Robin, Least Connection, IP Hash, Random, Weighted

1. Round Robin (Default) — กระจาย request ไปทีละตัวตามลำดับ

upstream backend {
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
    server 10.0.0.3:8080;
}

2. Weighted Round Robin — ให้น้ำหนักต่างกันตามสเปคเครื่อง

upstream backend {
    server 10.0.0.1:8080 weight=3;   # รับ 3 ใน 6 request
    server 10.0.0.2:8080 weight=2;   # รับ 2 ใน 6 request
    server 10.0.0.3:8080 weight=1;   # รับ 1 ใน 6 request
}

3. Least Connection — ส่งไปยัง Backend ที่มี active connection น้อยที่สุด

upstream backend {
    least_conn;
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
    server 10.0.0.3:8080;
}

4. IP Hash — แต่ละ IP ของ client จะถูก map ไปยัง backend ตัวเดิมเสมอ (Sticky Session แบบง่าย)

upstream backend {
    ip_hash;
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
}

5. Hash (กำหนด key เอง) — ใช้ key อื่นแทน IP เช่น URI, cookie

upstream backend {
    hash $request_uri consistent;    # consistent hashing — ลด disruption เมื่อเปลี่ยน server
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
}

6. Random — สุ่มเลือก backend (กระจาย load ได้ดีในระบบใหญ่)

upstream backend {
    random two least_conn;          # สุ่ม 2 ตัวแล้วเลือกที่ connection น้อยกว่า
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
    server 10.0.0.3:8080;
}

สูตรการกระจาย Weighted Round Robin เมื่อกำหนดน้ำหนัก w1,w2,,wn สัดส่วนที่ Backend ตัวที่ i จะได้รับ request คือ

Pi = wi j=1nwj

โดย:

ตัวอย่าง: weight = 3, 2, 1 ⇒ P1=50%, P233%, P317%

9.6.3 Passive vs Active Health Check

Passive Health Check — Nginx ตรวจสุขภาพจาก request จริงที่ส่งไป (ทำได้ใน OSS)

upstream backend {
    # หาก fail 3 ครั้งใน 30 วินาที → ทำเครื่องหมายไม่พร้อมใช้ 30 วินาที
    server 10.0.0.1:8080 max_fails=3 fail_timeout=30s;
    server 10.0.0.2:8080 max_fails=3 fail_timeout=30s;
    server 10.0.0.3:8080 max_fails=3 fail_timeout=30s;
}

Active Health Check — Nginx ส่ง request ไปตรวจสุขภาพ backend เป็นประจำ (มีเฉพาะใน NGINX Plus หรือต้องใช้ module เสริมเช่น nginx_upstream_check_module)

9.6.4 Sticky Session

# วิธีที่ 1: ip_hash (OSS) — sticky ตาม IP
upstream backend {
    ip_hash;
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
}

# วิธีที่ 2: hash by cookie (OSS)
upstream backend {
    hash $cookie_jsessionid consistent;
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
}

# วิธีที่ 3: sticky cookie (NGINX Plus)
# upstream backend {
#     sticky cookie srv_id expires=1h domain=.example.com path=/;
#     server 10.0.0.1:8080;
#     server 10.0.0.2:8080;
# }

9.6.5 Backup Server และ Fail-over

upstream backend {
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
    server 10.0.0.99:8080 backup;       # ใช้เมื่อ server หลักล่มทั้งหมด
    server 10.0.0.100:8080 down;        # ปิดชั่วคราว (ไว้สำหรับ maintenance)
}

9.7 SSL/TLS

9.7.1 แนวคิด PKI (Public Key Infrastructure)

PKI (Public Key Infrastructure) คือระบบที่ใช้บริหารจัดการกุญแจ (key) และใบรับรอง (certificate) ในระบบ Asymmetric Cryptography ประกอบด้วยส่วนหลักๆ คือ

sequenceDiagram
    participant C as Client (Browser)
    participant S as Server (Nginx)
    participant CA as Certificate Authority

    Note over CA: ออกใบรับรองให้ S ล่วงหน้า
    C->>S: ClientHello (TLS versions, ciphers)
    S->>C: ServerHello + Certificate (signed by CA)
    C->>C: ตรวจสอบลายเซ็นด้วย
Public Key ของ CA C->>S: Key Exchange (ECDHE) S->>C: Key Exchange Note over C,S: คำนวณ Session Key
(Symmetric) C->>S: Finished (encrypted) S->>C: Finished (encrypted) Note over C,S: 🔒 Encrypted Communication

9.7.2 Certificate Authority (CA), CSR, Private Key, Chain

ส่วนประกอบของ TLS Certificate

# สร้าง Private Key (RSA 4096 bit)
openssl genrsa -out example.com.key 4096

# หรือใช้ ECDSA (เร็วกว่า RSA ที่ความปลอดภัยเท่ากัน)
openssl ecparam -name prime256v1 -genkey -noout -out example.com.key

# สร้าง CSR
openssl req -new -key example.com.key -out example.com.csr \
    -subj "/C=TH/ST=Pathum Thani/L=Thanyaburi/O=RMUTSV/CN=example.com"

# สร้าง Self-signed Certificate (สำหรับ dev/test)
openssl req -x509 -new -key example.com.key -out example.com.crt -days 365 \
    -subj "/C=TH/ST=Pathum Thani/L=Thanyaburi/O=RMUTSV/CN=example.com"

# ตรวจสอบรายละเอียด certificate
openssl x509 -in example.com.crt -text -noout

9.7.3 Let's Encrypt และ Certbot (nginx plugin)

Let's Encrypt เป็น CA ที่ออกใบรับรอง ฟรี และอัตโนมัติผ่าน ACME Protocol — เป็นทางเลือกหลักของเว็บไซต์ทั่วไปในปัจจุบัน

# ติดตั้ง Certbot บน Ubuntu/Debian
sudo apt install -y certbot python3-certbot-nginx

# ขอ certificate และตั้งค่า Nginx อัตโนมัติ
sudo certbot --nginx -d example.com -d www.example.com

# ขอ certificate แต่ตั้งค่า Nginx เอง (manual mode)
sudo certbot certonly --nginx -d example.com -d www.example.com

# ทดสอบการต่ออายุอัตโนมัติ
sudo certbot renew --dry-run

# Certbot ติดตั้ง systemd timer ไว้ให้แล้ว
sudo systemctl status certbot.timer

ที่อยู่ของ certificate ที่ Certbot สร้าง

/etc/letsencrypt/live/example.com/
├── cert.pem        # certificate เฉพาะ
├── chain.pem       # intermediate chain
├── fullchain.pem   # cert.pem + chain.pem (ใช้ใน Nginx)
└── privkey.pem     # private key

9.7.4 acme.sh และ DNS Challenge

acme.sh เป็น shell script ที่ implement ACME protocol ใช้แทน Certbot ได้ และรองรับ DNS-01 Challenge ที่ขอ wildcard certificate ได้

# ติดตั้ง acme.sh
curl https://get.acme.sh | sh

# ขอ certificate ผ่าน DNS challenge (ตัวอย่างใช้ Cloudflare API)
export CF_Token="your_cloudflare_api_token"
~/.acme.sh/acme.sh --issue --dns dns_cf -d example.com -d '*.example.com'

# ติดตั้ง certificate ลงใน nginx
~/.acme.sh/acme.sh --install-cert -d example.com \
    --key-file       /etc/nginx/ssl/example.com.key  \
    --fullchain-file /etc/nginx/ssl/example.com.crt  \
    --reloadcmd      "systemctl reload nginx"

9.7.5 HTTP to HTTPS Redirect

# Server block สำหรับ HTTP — redirect ไป HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    # Redirect ทุก request ไป HTTPS
    return 301 https://$host$request_uri;
}

# Server block สำหรับ HTTPS
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com www.example.com;

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

    root /var/www/example.com;
    index index.html;
}

9.7.6 HSTS, OCSP Stapling

HSTS (HTTP Strict Transport Security) — บอก Browser ให้ใช้ HTTPS เท่านั้น ป้องกัน Downgrade Attack

# เพิ่มใน server block ของ HTTPS
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# max-age=63072000 = 2 ปี

OCSP Stapling — Nginx ดึงข้อมูลการเพิกถอน certificate จาก CA ล่วงหน้า แล้ว "staple" ใน TLS handshake ลด round-trip ของ Browser

ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;

9.7.7 TLS Version และ Cipher Suite Hardening

Configuration ที่แนะนำสำหรับ TLS modern (Mozilla SSL Config Generator — Intermediate)

# /etc/nginx/snippets/ssl-params.conf
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;     # ใน TLS 1.3 client เป็นคนเลือก
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

# Session
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;

# Diffie-Hellman parameter (สร้างไฟล์เองครั้งเดียว)
# openssl dhparam -out /etc/nginx/dhparam.pem 4096
ssl_dhparam /etc/nginx/dhparam.pem;

9.7.8 Wildcard และ SAN Certificate

server {
    listen 443 ssl http2;
    server_name example.com *.example.com api.example.org;

    # Certificate ที่มี SAN รวม example.com, *.example.com, api.example.org
    ssl_certificate     /etc/nginx/ssl/multi-domain.crt;
    ssl_certificate_key /etc/nginx/ssl/multi-domain.key;
}

9.8 Caching และ Compression

9.8.1 proxy_cache (key, zone, valid)

Proxy Cache ทำให้ Nginx เก็บ response จาก backend ไว้ใน disk/memory ลดภาระของ backend

http {
    # กำหนด cache zone
    proxy_cache_path /var/cache/nginx/api
                     levels=1:2
                     keys_zone=api_cache:10m
                     max_size=1g
                     inactive=60m
                     use_temp_path=off;

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

        location / {
            proxy_pass http://backend;

            # เปิด cache
            proxy_cache api_cache;
            proxy_cache_key "$scheme$request_method$host$request_uri";

            # cache เฉพาะ status code
            proxy_cache_valid 200 302 10m;
            proxy_cache_valid 404 1m;

            # cache lock — ป้องกันคำขอเดียวกันส่งไป backend หลายครั้ง
            proxy_cache_lock on;
            proxy_cache_lock_timeout 5s;

            # ใช้ cache เก่าเมื่อ backend ล่ม
            proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;

            # bypass cache ตามเงื่อนไข
            proxy_cache_bypass $http_pragma $http_authorization;
            proxy_no_cache $http_authorization;

            # debug header
            add_header X-Cache-Status $upstream_cache_status;
        }
    }
}

ค่า $upstream_cache_status มีได้ 6 ค่า

ค่า ความหมาย
MISS ไม่มีใน cache → ดึงจาก backend
HIT พบใน cache → ส่งให้ client
EXPIRED มีใน cache แต่หมดอายุ → ดึงใหม่
STALE มีของเก่าและใช้ของเก่าเพราะ backend error
UPDATING มีของเก่ากำลังอัพเดต
BYPASS ตรงเงื่อนไข proxy_cache_bypass

9.8.2 Browser Cache: Cache-Control, ETag, Expires

# Static asset — cache ที่ browser นาน + immutable (ไม่ตรวจสอบใหม่)
location ~* \.(jpg|jpeg|png|gif|webp|svg|css|js|woff|woff2|ttf|eot)$ {
    expires 30d;
    add_header Cache-Control "public, immutable, max-age=2592000";
    access_log off;
}

# HTML — ตรวจสอบใหม่ทุกครั้ง
location ~* \.html$ {
    expires -1;
    add_header Cache-Control "no-cache, must-revalidate";
}

# API — ห้าม cache
location /api/ {
    proxy_pass http://backend;
    add_header Cache-Control "no-store";
}

# เปิด ETag (default เปิดอยู่แล้ว)
etag on;

9.8.3 gzip และ brotli compression

gzip — รองรับใน Nginx ทุกเวอร์ชัน

http {
    gzip on;
    gzip_vary on;                       # เพิ่ม Vary: Accept-Encoding header
    gzip_min_length 1024;               # ไม่บีบไฟล์เล็กกว่า 1KB
    gzip_proxied any;                   # บีบ response ที่มาจาก proxy
    gzip_comp_level 6;                  # 1-9 (สูง = บีบดีแต่ใช้ CPU มาก)
    gzip_types
        text/plain
        text/css
        text/xml
        text/javascript
        application/json
        application/javascript
        application/xml
        application/xml+rss
        application/atom+xml
        image/svg+xml
        font/ttf
        font/otf;
    gzip_disable "msie6";
}

brotli — บีบอัดดีกว่า gzip 15-20% แต่ต้องติดตั้ง module เพิ่ม (ngx_brotli)

brotli on;
brotli_comp_level 6;
brotli_static on;                       # เสิร์ฟไฟล์ .br ที่บีบไว้ล่วงหน้า
brotli_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;

9.8.4 FastCGI cache, uWSGI cache

ใช้คล้ายกับ proxy_cache แต่สำหรับ PHP-FPM (FastCGI)

http {
    fastcgi_cache_path /var/cache/nginx/fcgi
                       levels=1:2
                       keys_zone=php_cache:10m
                       max_size=500m
                       inactive=60m;

    server {
        location ~ \.php$ {
            include snippets/fastcgi-php.conf;
            fastcgi_pass unix:/run/php/php8.2-fpm.sock;

            fastcgi_cache php_cache;
            fastcgi_cache_key "$scheme$request_method$host$request_uri";
            fastcgi_cache_valid 200 5m;
            fastcgi_cache_use_stale error timeout updating;

            add_header X-FastCGI-Cache $upstream_cache_status;
        }
    }
}

9.9 Caddy Web Server

Caddy เป็น Web Server ที่เขียนด้วย Go มี Automatic HTTPS เป็น default — เพียงระบุโดเมน Caddy จะขอ certificate จาก Let's Encrypt อัตโนมัติ

9.9.1 ปรัชญาของ Caddy (Automatic HTTPS by default)

แนวคิดหลักของ Caddy

  1. HTTPS ต้องเป็นค่า default — ไม่ใช่ option
  2. Configuration ต้องเรียบง่าย — Caddyfile อ่านง่ายกว่า nginx.conf
  3. Cross-platform — เป็น single binary ไม่มี dependency
  4. Modular — ขยายได้ผ่าน plugin (xcaddy)
  5. API-driven — config ผ่าน JSON API ได้

9.9.2 การติดตั้ง Caddy

บน Debian/Ubuntu

# ติดตั้ง dependency
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl

# เพิ่ม Caddy GPG key และ repository
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
    | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
    | sudo tee /etc/apt/sources.list.d/caddy-stable.list

# ติดตั้ง
sudo apt update
sudo apt install -y caddy

# ตรวจสอบเวอร์ชัน
caddy version

บน Arch Linux

sudo pacman -S caddy
sudo systemctl enable --now caddy

ผ่าน Docker

docker run -d --name caddy \
    -p 80:80 -p 443:443 -p 443:443/udp \
    -v $PWD/Caddyfile:/etc/caddy/Caddyfile \
    -v caddy_data:/data \
    -v caddy_config:/config \
    caddy:latest

9.9.3 Caddyfile Syntax (เรียบง่ายกว่า Nginx)

ตัวอย่าง Caddyfile ขั้นพื้นฐาน

# /etc/caddy/Caddyfile

# Static site — Caddy ขอ HTTPS ให้อัตโนมัติ!
example.com {
    root * /var/www/example.com
    file_server
}

# Reverse proxy
api.example.com {
    reverse_proxy localhost:3000
}

# Multiple hosts
blog.example.com, www.blog.example.com {
    root * /var/www/blog
    encode gzip zstd
    file_server
    try_files {path} {path}/ /index.html
}

เทียบกับ Nginx — Caddyfile สั้นและอ่านง่ายกว่ามาก

คุณสมบัติ Nginx Caddy
HTTPS ต้องตั้งค่าเอง + Certbot อัตโนมัติ
Config size ~20-30 บรรทัด ~5 บรรทัด
Reload nginx -s reload caddy reload
HTTP/3 ต้อง compile เพิ่ม built-in

9.9.4 File Server

example.com {
    root * /srv/www
    encode gzip zstd                # บีบอัด

    # SPA routing
    try_files {path} /index.html

    file_server {
        hide .git
        index index.html index.htm
        browse                       # เปิดให้เห็น directory listing
    }

    # Custom error
    handle_errors {
        rewrite * /errors/{err.status_code}.html
        file_server
    }
}

9.9.5 Reverse Proxy: reverse_proxy

# Reverse proxy พื้นฐาน
api.example.com {
    reverse_proxy localhost:8080
}

# Reverse proxy + load balancing
app.example.com {
    reverse_proxy {
        to 10.0.0.1:8080 10.0.0.2:8080 10.0.0.3:8080
        lb_policy least_conn
        health_uri /health
        health_interval 10s
        health_timeout 5s
    }
}

# Reverse proxy พร้อม path routing
example.com {
    handle /api/* {
        uri strip_prefix /api
        reverse_proxy localhost:3000
    }

    handle /admin/* {
        reverse_proxy localhost:4000
    }

    handle {
        root * /var/www/example.com
        file_server
    }
}

# WebSocket — Caddy รองรับ WebSocket อัตโนมัติ ไม่ต้องตั้งค่าเพิ่ม
ws.example.com {
    reverse_proxy localhost:8765
}

9.9.6 Automatic Let's Encrypt / ZeroSSL

Caddy ขอ certificate อัตโนมัติเมื่อ:

  1. ระบุโดเมนใน Caddyfile (ไม่ใช่ IP หรือ localhost)
  2. โดเมนนั้น resolve มาที่ Server ได้
  3. Port 80 และ 443 เปิดให้ Caddy ใช้งาน
# ระบุ email สำหรับ Let's Encrypt (recommended)
{
    email admin@example.com
    # acme_ca https://acme-staging-v02.api.letsencrypt.org/directory  # ใช้ staging ตอนทดสอบ
}

example.com {
    file_server
}

สำหรับ wildcard ต้องใช้ DNS challenge ซึ่งต้องใช้ Caddy ที่ build รวม DNS provider plugin

*.example.com {
    tls {
        dns cloudflare {env.CF_API_TOKEN}
    }
    reverse_proxy localhost:8080
}

9.9.7 Caddy Module และ Plugin (xcaddy)

xcaddy เป็นเครื่องมือ build Caddy พร้อม plugin

# ติดตั้ง xcaddy
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest

# Build Caddy พร้อม plugin
xcaddy build \
    --with github.com/caddy-dns/cloudflare \
    --with github.com/greenpau/caddy-security

# แทนที่ binary
sudo cp ./caddy /usr/bin/caddy
sudo systemctl restart caddy

9.9.8 JSON Config API

Caddy มี Admin API ที่ฟัง localhost:2019 ปรับ config ได้แบบ real-time ผ่าน HTTP

# ดู config ปัจจุบัน
curl localhost:2019/config/ | jq

# โหลด config ใหม่จากไฟล์ JSON
curl -X POST localhost:2019/load \
    -H "Content-Type: application/json" \
    -d @config.json

# ดู metric
curl localhost:2019/metrics

ตัวอย่าง config.json

{
  "apps": {
    "http": {
      "servers": {
        "example": {
          "listen": [":443"],
          "routes": [
            {
              "match": [{"host": ["example.com"]}],
              "handle": [
                {
                  "handler": "reverse_proxy",
                  "upstreams": [{"dial": "localhost:3000"}]
                }
              ]
            }
          ]
        }
      }
    }
  }
}

9.10 Nginx vs Caddy vs อื่น ๆ

9.10.1 Apache HTTP Server (httpd)

Apache HTTPD เป็น Web Server เก่าแก่ตั้งแต่ปี 1995 — ยังใช้แพร่หลายใน shared hosting

จุดเด่น

จุดด้อยเทียบกับ Nginx

9.10.2 HAProxy (L4/L7 Load Balancer)

HAProxy เป็น Load Balancer ที่ออกแบบมาเพื่อ Load Balancing โดยเฉพาะ — ไม่ใช่ Web Server เต็มตัว

จุดเด่น

9.10.3 Traefik (Container-native)

Traefik เป็น Reverse Proxy ที่ออกแบบมาเพื่อ container/cloud-native โดยเฉพาะ

จุดเด่น

# ตัวอย่าง docker-compose.yml ใช้ Traefik
services:
  traefik:
    image: traefik:v3.0
    command:
      - "--providers.docker=true"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.le.acme.email=admin@example.com"
      - "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
      - "--certificatesresolvers.le.acme.tlschallenge=true"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt

  app:
    image: nginx
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`example.com`)"
      - "traefik.http.routers.app.entrypoints=websecure"
      - "traefik.http.routers.app.tls.certresolver=le"

9.10.4 Envoy, Pingora

9.10.5 การเปรียบเทียบ: ประสิทธิภาพ, ความง่ายในการตั้งค่า, Ecosystem

คุณสมบัติ Nginx Caddy Apache HAProxy Traefik
ภาษา C Go C C Go
Performance (req/s) สูงมาก สูง กลาง สูงสุด (L4/L7) สูง
Memory ต่ำ กลาง สูง ต่ำมาก กลาง
HTTPS อัตโนมัติ ❌ (ใช้ Certbot)
HTTP/3 บางส่วน
Config Style Block-based Caddyfile/JSON XML-like INI-like YAML/Label
Hot Reload ✅ (graceful)
Service Discovery ❌ (ต้อง 3rd party)
Dashboard ✅ (mod_status)
Use Case Production web/proxy เว็บเล็ก-กลาง, dev Shared hosting LB เฉพาะ Container/K8s

คำแนะนำในการเลือก


9.11 Security และ Hardening

9.11.1 Rate Limiting (limit_req, limit_conn)

Rate Limiting ป้องกัน abuse และ brute force ด้วย Token Bucket / Leaky Bucket algorithm

http {
    # กำหนด rate limit zone
    # 1m = 1MB ≈ 16,000 IP addresses
    # 10r/s = 10 requests per second
    limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
    limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
    limit_req_zone $binary_remote_addr zone=api:10m rate=20r/s;

    # Connection limit
    limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;

    server {
        listen 80;
        server_name example.com;

        # Apply rate limit ทั่วไป (allow burst 20, ไม่ delay)
        limit_req zone=general burst=20 nodelay;

        # Connection limit ต่อ IP
        limit_conn conn_per_ip 10;

        # Login endpoint — เข้มกว่า
        location /login {
            limit_req zone=login burst=5 nodelay;
            proxy_pass http://backend;
        }

        # API — rate limit ตาม API key (header)
        location /api/ {
            limit_req zone=api burst=50;
            proxy_pass http://backend;
        }

        # Custom error สำหรับ rate limit
        error_page 503 /rate_limited.html;
    }
}

สูตรคำนวณ Token Bucket

tokens ( t ) = min ( capacity , tokens ( t 1 ) + rate × Δ t )

โดย:

แต่ละ request ใช้ 1 token ถ้า token ไม่พอ → ตอบ 503 Service Unavailable

9.11.2 Web Application Firewall (ModSecurity, Coraza)

ModSecurity — WAF เก่าแก่ที่สุด รองรับ OWASP Core Rule Set (CRS)

# ติดตั้ง ModSecurity-nginx connector (Ubuntu)
sudo apt install -y libnginx-mod-http-modsecurity

# ดาวน์โหลด OWASP CRS
sudo git clone https://github.com/coreruleset/coreruleset /etc/nginx/modsec/crs
cd /etc/nginx/modsec/crs
sudo cp crs-setup.conf.example crs-setup.conf
# /etc/nginx/nginx.conf
load_module modules/ngx_http_modsecurity_module.so;

server {
    listen 80;
    server_name example.com;

    modsecurity on;
    modsecurity_rules_file /etc/nginx/modsec/main.conf;

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

Coraza — WAF เขียนด้วย Go ที่เข้ากันได้กับ ModSecurity rules ใช้กับ Caddy/Envoy ได้

9.11.3 DDoS Mitigation เบื้องต้น

http {
    # Slow connection attack mitigation
    client_body_timeout 10s;
    client_header_timeout 10s;
    keepalive_timeout 30s;
    send_timeout 10s;

    # จำกัดขนาด request
    client_max_body_size 10M;
    client_body_buffer_size 16k;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 8k;

    # จำกัด method
    server {
        if ($request_method !~ ^(GET|HEAD|POST|PUT|DELETE|PATCH|OPTIONS)$) {
            return 405;
        }

        # Block bot ชั่วๆ
        if ($http_user_agent ~* (Scrapy|Wget|curl/)) {
            return 403;
        }
    }
}

หมายเหตุ: Layer 3/4 DDoS ต้องอาศัย service ภายนอกเช่น Cloudflare, AWS Shield หรือ DDoS protection ของ ISP — Nginx ป้องกันได้แค่ Layer 7

9.11.4 Hide Server Version (server_tokens off)

http {
    # ซ่อนเวอร์ชัน Nginx ใน Server header และ error page
    server_tokens off;

    # ซ่อน header เพิ่มเติม (ต้องใช้ module headers-more)
    # more_clear_headers Server;
    # more_set_headers "Server: SecretServer";
}

9.11.5 Security Header: CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy

Security Header ที่แนะนำให้ตั้งทุก server

# /etc/nginx/snippets/security-headers.conf

# ป้องกัน Clickjacking — ห้ามฝัง iframe จากที่อื่น
add_header X-Frame-Options "SAMEORIGIN" always;

# ห้าม browser เดา MIME type
add_header X-Content-Type-Options "nosniff" always;

# XSS Protection (deprecated ใน browser ใหม่ แต่ยังคงใส่เพื่อ legacy)
add_header X-XSS-Protection "1; mode=block" always;

# ควบคุม Referer ที่ส่งไปเว็บอื่น
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# ปิดความสามารถของเว็บที่ไม่จำเป็น
add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=()" always;

# Content Security Policy — ป้องกัน XSS และ Data Injection
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self'; frame-ancestors 'self';" always;

# HSTS (เฉพาะ HTTPS)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

# Cross-Origin policies (ป้องกัน Spectre)
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;

การใช้งาน

server {
    listen 443 ssl http2;
    server_name example.com;

    include snippets/ssl-params.conf;
    include snippets/security-headers.conf;

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

    root /var/www/example.com;
    index index.html;
}

ทดสอบ Security Header ใช้บริการ


บทสรุปบทเรียน

บทเรียนนี้ครอบคลุมการใช้งาน Nginx และ Caddy ในฐานะ Web Server และ Reverse Proxy ตั้งแต่แนวคิดของ HTTP/HTTPS, การติดตั้งและตั้งค่า, การทำ Virtual Host, Reverse Proxy, Load Balancing, SSL/TLS ด้วย Let's Encrypt, Caching, ไปจนถึง Security Hardening

ข้อแนะนำในการนำไปใช้งานจริง

  1. ใช้ Nginx สำหรับเว็บที่มี traffic สูงและต้องการการปรับแต่งเชิงลึก
  2. ใช้ Caddy เมื่อต้องการตั้งค่ารวดเร็วและ HTTPS อัตโนมัติ
  3. ทดสอบ config เสมอ ด้วย nginx -t หรือ caddy validate ก่อน reload
  4. เปิด HTTPS เป็น default ทุกเว็บไซต์ (Let's Encrypt ฟรี)
  5. ตั้ง Security Header ทุก response เพื่อป้องกัน attack ที่พบบ่อย
  6. ทำ Rate Limiting สำหรับ endpoint ที่มีโอกาสถูก abuse (login, API)
  7. Monitor log อย่างสม่ำเสมอเพื่อจับ pattern ผิดปกติ