Nginx from Zero to Production · Part 2 — The Configuration Model
The grammar behind every Nginx config: directives and contexts (main/events/http/server/location), how location matching really works, try_files, virtual hosts with server_name, and the safe reload workflow — with exercises.
Ở Phần 1 bạn đã cài Nginx và phục vụ một trang. Giờ ta học ngôn ngữ của config — vì 90% “lỗi Nginx” thực ra là “tôi chưa hiểu mô hình config”.
Nắm vững phần này thì bạn đọc được mọi config Nginx trên mạng.
1. Directive và context
Một config Nginx gồm hai thứ:
- Directive — câu lệnh, kết thúc bằng
;. Ví dụ:listen 80;. - Context (block) — các phần
{ }nhóm directive lại và định nghĩa phạm vi.
Context lồng nhau như búp bê Nga:
main context (the file itself — global settings)
│ worker_processes auto;
│
├── events { ... } # connection-processing settings
│
└── http { ... } # everything HTTP lives here
│ include mime.types;
│ gzip on;
│
├── server { ... } # one virtual host (one site)
│ │ listen 80;
│ │ server_name example.com;
│ │
│ └── location / { ... } # rules for a set of URLs
│
└── server { ... } # another site on the same Nginx
Quy tắc then chốt: context con kế thừa directive từ cha, và có thể ghi đè. Đặt gzip on; trong http thì mọi server kế thừa, trừ khi một server cụ thể nói khác.
Một config tối giản nhưng đầy đủ trông như sau:
# main context
worker_processes auto; # one worker per CPU core
events {
worker_connections 1024; # max simultaneous connections per worker
}
http {
include mime.types; # map file extensions → Content-Type
default_type application/octet-stream;
server {
listen 80;
server_name localhost;
location / {
root /var/www/lab;
index index.html;
}
}
}
worker_processes × worker_connectionsxấp xỉ số kết nối đồng thời tối đa.autoworker × 1024 là quá đủ cho việc local.
2. Block server — virtual host
Một Nginx có thể host nhiều site. Mỗi block server là một virtual host. Nginx chọn server nào xử lý request bằng hai thứ: cổng/địa chỉ listen, và server_name khớp với Host header của request.
http {
server {
listen 80;
server_name site-a.local; # http://site-a.local
root /var/www/site-a;
}
server {
listen 80;
server_name site-b.local; # http://site-b.local
root /var/www/site-b;
}
}
Cả hai nghe ở cổng 80, nhưng Host header quyết định ai thắng. Test cục bộ bằng cách giả Host header:
curl -H 'Host: site-a.local' http://localhost # → site-a
curl -H 'Host: site-b.local' http://localhost # → site-b
Nếu không server_name nào khớp, Nginx dùng server mặc định cho cổng đó (cái đầu tiên, hoặc block đánh dấu listen 80 default_server;).
3. location — trái tim của routing
Bên trong server, block location quyết định làm gì với các đường dẫn URL khác nhau. Đây là nơi phần lớn logic config của bạn nằm.
Có vài kiểu khớp, và chúng có thứ tự ưu tiên nghiêm ngặt — không phải trên-xuống-dưới như bạn tưởng:
server {
listen 80;
server_name localhost;
location = /health { return 200 "ok\n"; } # 1. EXACT match
location ^~ /assets/ { root /var/www; } # 2. PREFIX, stop regex
location ~ \.php$ { return 403; } # 3. REGEX (case-sensitive)
location ~* \.(jpg|png)$ { expires 30d; } # 3. REGEX (case-insensitive)
location / { root /var/www/lab; } # 4. PREFIX (longest wins)
}
Thuật toán khớp Nginx thực sự chạy:
1. `location = /path` exact match → if hit, STOP. highest priority.
2. `location ^~ /prefix` prefix, then STOP → longest matching prefix; skip regex.
3. `location ~ regex` regex (case-sensitive) → first matching regex IN FILE ORDER.
`location ~* regex` regex (case-insensitive)
4. `location /prefix` plain prefix → used only if no regex matched.
longest prefix wins.
Hai sự thật làm ai cũng bất ngờ:
- Khớp chính xác (
=) thắng tất cả — tuyệt cho/healthhoặc/favicon.ico(khớp nhanh nhất có thể). - Regex được xét theo thứ tự trong file, còn prefix thường thì theo khớp dài nhất. Nên sắp xếp lại prefix thường không đổi gì, nhưng sắp xếp lại regex thì có.
Test nhanh:
curl http://localhost/health # → ok (exact match wins)
curl -I http://localhost/cat.jpg # → has "Expires" header (regex ~* matched)
4. root vs alias — cái bẫy kinh điển
Cả hai ánh xạ URL tới đĩa, nhưng chúng ghép đường dẫn khác nhau. Đây là lỗi file tĩnh phổ biến nhất.
# root: the location path is APPENDED to root
location /assets/ {
root /var/www; # request /assets/app.js → /var/www/assets/app.js
}
# alias: the location path is REPLACED by alias
location /assets/ {
alias /var/www/static/; # request /assets/app.js → /var/www/static/app.js
}
Mẹo nhớ: root cộng thêm, alias thay thế. Với alias, luôn kết thúc cả location lẫn alias bằng / để tránh bất ngờ.
5. try_files — phục vụ file, dự phòng mượt mà
try_files thử một danh sách đường dẫn theo thứ tự và dùng cái đầu tiên tồn tại. Nó thiết yếu cho hai tình huống hằng ngày.
Site tĩnh với trang 404 tuỳ chỉnh:
location / {
root /var/www/lab;
try_files $uri $uri/ =404;
# 1) try the exact file ($uri)
# 2) try it as a directory ($uri/)
# 3) otherwise return 404
}
Single Page App (SPA React/Vue/Astro) — mọi route lạ phải dự phòng về index.html để router phía client xử lý:
location / {
root /var/www/spa;
try_files $uri $uri/ /index.html; # the magic line for SPAs
}
Thiếu dòng cuối đó, refresh /dashboard trong SPA sẽ 404 — vì không có file dashboard trên đĩa. try_files viết lại nó thành index.html, và router JS tiếp quản.
6. MIME type & default_type
Trình duyệt quyết định xử lý response thế nào qua Content-Type header. Nginx đặt nó từ phần mở rộng file dùng bản đồ mime.types:
http {
include mime.types; # .css → text/css, .js → text/javascript ...
default_type application/octet-stream; # fallback for unknown extensions
}
Quên include mime.types; thì CSS của bạn về dưới dạng text/plain — trình duyệt từ chối áp dụng, và lỗi “mất style” của bạn chẳng liên quan gì tới chính file CSS.
7. Quy trình sửa-và-reload an toàn
Lặp vòng này cho mọi thay đổi trong phần còn lại của series:
# 1. Edit a config file
# 2. ALWAYS validate first — catches typos before they go live
sudo nginx -t
# nginx: configuration file /etc/nginx/nginx.conf test is successful
# 3. Apply with a graceful reload (no dropped connections)
sudo nginx -s reload
# 4. If a request misbehaves, read the error log
sudo tail -f /var/log/nginx/error.log
Tại sao reload mà không restart? reload giữ kết nối hiện có sống trong khi đổi config; restart ngắt mọi thứ trong chốc lát. Mặc định dùng reload.
8. Tóm tắt
- Config = directive (
;) bên trong context ({ }); con kế thừa từ cha. - Block
serverlà virtual host, chọn bằnglisten+server_name. - Khớp
locationcó ưu tiên nghiêm ngặt: chính xác → prefix^~→ regex (thứ tự file) → prefix thường (dài nhất). rootcộng,aliasthay.try_files $uri $uri/ /index.html;là phao cứu sinh cho SPA.
Tiếp — Phần 3: reverse proxy & load balancing. Ta sẽ đặt Nginx trước một app backend thật.
Bài tập
- Hai site, một Nginx: tạo hai block
server(a.local,b.local) ở cổng 80 với root khác nhau; xác minh từng cái bằngcurl -H 'Host: ...'. - Đoán kết quả khớp: với các block
locationở §3, đoán cái nào xử lý/health,/assets/app.js,/photo.JPG, và/about— rồi test cả bốn. - root vs alias: phục vụ
/assets/app.jstừ một thư mục bằngroot, rồi viết lại bằngaliastrỏ tới thư mục tên khác. - Dự phòng SPA: build một SPA bất kỳ (hoặc giả bằng file route rỗng), cấu hình
try_files, và xác nhận refresh một route sâu phục vụindex.htmlthay vì 404. - Làm hỏng MIME: comment dòng
include mime.types;, reload, và quan sát CSS về dưới dạngtext/plaintrong DevTools. - Nâng cao: thêm
location = /health { return 200 "ok\n"; }và xác nhận nó phản hồi mà không chạm đĩa.