
บทเรียนนี้ครอบคลุมแนวคิดของ Web Server, การติดตั้งและตั้งค่า Nginx, การทำ Reverse Proxy, Load Balancing, SSL/TLS, Caching รวมถึง Caddy ซึ่งเป็น Web Server ทางเลือกที่ตั้งค่าง่ายและรองรับ HTTPS อัตโนมัติ พร้อมตัวอย่างการใช้งานจริงที่นำไปประยุกต์ได้ทันที
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
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)
วงจรการสื่อสารพื้นฐาน (Request-Response Cycle) ของ HTTP ประกอบด้วยขั้นตอน
โครงสร้างของ 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"}
HTTP Method บ่งบอกการกระทำที่ต้องการให้ทำกับทรัพยากร (Resource) ในระบบ
| Method | ใช้ทำอะไร | Idempotent | Safe | มี Body |
|---|---|---|---|---|
| GET | ดึงข้อมูล | ✅ | ✅ | ❌ (โดยทั่วไป) |
| POST | สร้างข้อมูลใหม่ / ส่งข้อมูล | ❌ | ❌ | ✅ |
| PUT | แทนที่ข้อมูลทั้งก้อน | ✅ | ❌ | ✅ |
| PATCH | แก้ไขข้อมูลบางส่วน | ❌ | ❌ | ✅ |
| DELETE | ลบข้อมูล | ✅ | ❌ | ❌ |
| HEAD | ดึงเฉพาะ Header (ไม่มี Body) | ✅ | ✅ | ❌ |
| OPTIONS | ตรวจสอบความสามารถของ Server (CORS Preflight) | ✅ | ✅ | ❌ |
Idempotent = ส่งซ้ำกี่ครั้งก็ให้ผลเดียวกัน, Safe = ไม่ทำให้ State ของ Server เปลี่ยน
HTTP Status Code เป็นเลข 3 หลักที่บ่งบอกผลลัพธ์ของการประมวลผล แบ่งเป็น 5 กลุ่ม
100 Continue, 101 Switching Protocols (WebSocket)200 OK, 201 Created, 204 No Content, 206 Partial Content301 Moved Permanently, 302 Found, 304 Not Modified, 307 Temporary Redirect, 308 Permanent Redirect400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 405 Method Not Allowed, 409 Conflict, 422 Unprocessable Entity, 429 Too Many Requests500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway TimeoutHTTP Header คือคู่ Key-Value ที่ใช้แลกเปลี่ยน Metadata ระหว่าง Client กับ Server แบ่งเป็น
Date, Connection, Cache-ControlHost, User-Agent, Accept, Authorization, Cookie, If-None-MatchServer, Content-Type, Content-Length, Set-Cookie, ETag, LocationContent-Encoding, Content-Language, Content-DispositionCookie คือไฟล์เล็กๆ ที่ Server ส่งไปเก็บที่ Client ผ่าน Header Set-Cookie และ Client จะส่งกลับมาทุกครั้งที่ Request ผ่าน Header Cookie
Set-Cookie: session_id=abc123; Path=/; HttpOnly; Secure; SameSite=Strict; Max-Age=3600
Cookie Attribute สำคัญ:
HttpOnly — ป้องกัน JavaScript เข้าถึง (ลด XSS)Secure — ส่งเฉพาะผ่าน HTTPS เท่านั้นSameSite — Strict / Lax / None (ป้องกัน CSRF)Max-Age / Expires — อายุของ CookiePath / Domain — ขอบเขตที่ Cookie ใช้งานได้Session คือสถานะของผู้ใช้ที่ Server เก็บไว้ โดยปกติจะใช้ Cookie เก็บ session_id เพื่ออ้างอิงข้อมูล Session ที่ฝั่ง Server (อาจเก็บใน Redis, Memcached, Database)
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 |
MIME Type (Multipurpose Internet Mail Extensions) ใช้บอกประเภทของข้อมูลที่ส่งผ่าน HTTP โดยส่งใน Header Content-Type มีรูปแบบ type/subtype เช่น
text/html — เอกสาร HTMLtext/css — สไตล์ชีตapplication/javascript — JavaScriptapplication/json — ข้อมูล JSONapplication/xml — ข้อมูล XMLimage/png, image/jpeg, image/webp, image/svg+xml — รูปภาพvideo/mp4, audio/mpeg — สื่อมัลติมีเดียapplication/pdf — เอกสาร PDFmultipart/form-data — ฟอร์มที่มีการอัปโหลดไฟล์application/octet-stream — ไฟล์ไบนารีทั่วไป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"}
Nginx (อ่านว่า "Engine-X") เป็น Web Server แบบ Event-driven ที่พัฒนาโดย Igor Sysoev ในปี 2004 เพื่อแก้ปัญหา C10K Problem (รองรับ 10,000 connection พร้อมกัน) ปัจจุบันถือเป็น Web Server ที่นิยมใช้งานบน Server Linux เป็นอันดับต้นๆ ของโลก
บน 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!" แสดงว่าติดตั้งสำเร็จ
/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/เก็บไฟล์ตั้งค่าทั้งหมดแทน
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
restart — หยุด process แล้วสร้างใหม่ทั้งหมด ทำให้ connection ที่ค้างอยู่หลุดทั้งหมดreload — ส่ง signal SIGHUP ให้ master process อ่าน config ใหม่ จากนั้น spawn worker ใหม่ และให้ worker เก่าจัดการ connection ที่ค้างอยู่จนเสร็จ (graceful)ก่อน 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 ถูกต้องเท่านั้น
โครงสร้างของไฟล์ตั้งค่า Nginx ใช้ syntax แบบ block-based ที่ประกอบด้วย Directive และ Context (Block) โดย Directive แต่ละตัวต้องลงท้ายด้วย semicolon (;)
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/*;
}
server_name, listen, root, indexDirective พื้นฐานที่ใช้ใน server block
listen — กำหนด Port และ IP ที่ Nginx จะรับ connectionserver_name — ระบุชื่อโดเมนของ Virtual Host (รองรับ wildcard และ regex)root — ไดเรกทอรีหลักที่เก็บไฟล์ของเว็บไซต์index — ชื่อไฟล์ default เมื่อเข้าถึงไดเรกทอรี# /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 จะอยู่ในส่วนถัดไป
}
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
=) ก่อน — ถ้าเจอใช้ทันที^~ → ใช้ทันที (ข้าม regex)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;
}
}
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แทนได้ เพราะเร็วกว่าและอ่านเข้าใจง่ายกว่า
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;
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;
}
Virtual Host (ใน Nginx เรียกว่า Server Block) คือกลไกที่ทำให้ Nginx ตัวเดียวสามารถโฮสต์เว็บไซต์ได้หลายเว็บบน Server เครื่องเดียว
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
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;
}
เมื่อมี 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 ตรงๆ หรือโดเมนที่ไม่ได้รับอนุญาต
รองรับ 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
}
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 |
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
}
เมื่อทำ 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) แต่ยังไม่ใช้แพร่หลาย
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;
}
}
ตัวอย่างที่ 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;
}
}
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;
}
Load Balancing คือการกระจาย request ไปยัง Backend หลายตัวเพื่อ
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;
}
}
}
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 เมื่อกำหนดน้ำหนัก สัดส่วนที่ Backend ตัวที่ จะได้รับ request คือ
โดย:
ตัวอย่าง: weight = 3, 2, 1 ⇒ , ,
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)
# วิธีที่ 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;
# }
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)
}
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
ส่วนประกอบของ 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
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
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"
# 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;
}
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;
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;
*.example.com แต่ไม่รวม *.sub.example.comserver {
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;
}
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 |
# 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;
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;
ใช้คล้ายกับ 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;
}
}
}
Caddy เป็น Web Server ที่เขียนด้วย Go มี Automatic HTTPS เป็น default — เพียงระบุโดเมน Caddy จะขอ certificate จาก Let's Encrypt อัตโนมัติ
แนวคิดหลักของ 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
ตัวอย่าง 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 |
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
}
}
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
}
Caddy ขอ certificate อัตโนมัติเมื่อ:
# ระบุ 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
}
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
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"}]
}
]
}
]
}
}
}
}
}
Apache HTTPD เป็น Web Server เก่าแก่ตั้งแต่ปี 1995 — ยังใช้แพร่หลายใน shared hosting
จุดเด่น
.htaccess ทำให้ผู้ใช้แก้ config ได้โดยไม่ต้องแก้ไฟล์หลักจุดด้อยเทียบกับ Nginx
HAProxy เป็น Load Balancer ที่ออกแบบมาเพื่อ Load Balancing โดยเฉพาะ — ไม่ใช่ Web Server เต็มตัว
จุดเด่น
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"
| คุณสมบัติ | 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 |
คำแนะนำในการเลือก
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
โดย:
แต่ละ request ใช้ 1 token ถ้า token ไม่พอ → ตอบ 503 Service Unavailable
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 ได้
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
http {
# ซ่อนเวอร์ชัน Nginx ใน Server header และ error page
server_tokens off;
# ซ่อน header เพิ่มเติม (ต้องใช้ module headers-more)
# more_clear_headers Server;
# more_set_headers "Server: SecretServer";
}
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
ข้อแนะนำในการนำไปใช้งานจริง
nginx -t หรือ caddy validate ก่อน reload