Nginx là một web server hiệu suất cao, được sử dụng để xử lý tải cho một số trang web lớn nhất trên internet. Nó đặc biệt mạnh trong việc xử lý nhiều kết nối đồng thời và rất hiệu quả khi chuyển tiếp hoặc phục vụ nội dung tĩnh.
Trong hướng dẫn này, chúng ta sẽ tập trung vào việc tìm hiểu chi tiết cấu trúc File cấu hình Nginx, cùng với một số nguyên tắc giúp bạn thiết kế file cấu hình hợp lý.
Tìm hiểu về các Context trong cấu hình Nginx
Hướng dẫn này sẽ trình bày cấu trúc của file cấu hình chính của Nginx. Vị trí của file này phụ thuộc vào cách Nginx được cài đặt. Trên nhiều bản phân phối Linux, file thường nằm tại đường dẫn /etc/nginx/nginx.conf
. Nếu không thấy ở đó, bạn cũng có thể tìm tại /usr/local/nginx/conf/nginx.conf
hoặc /usr/local/etc/nginx/nginx.conf
.
Một trong những điều đầu tiên bạn sẽ nhận ra khi xem file cấu hình chính là nó được tổ chức theo dạng cây, được đánh dấu bởi các cặp ngoặc nhọn ({
và }
). Trong tài liệu của Nginx, các khu vực được xác định bởi những cặp ngoặc này được gọi là “context”, vì chúng chứa các thiết lập cấu hình được phân chia theo từng phạm vi riêng biệt. Việc chia thành các context như vậy giúp tổ chức cấu hình hợp lý hơn và cho phép áp dụng một số logic điều kiện để quyết định có áp dụng các cấu hình bên trong hay không.
Vì các context có thể lồng nhau, Nginx cho phép kế thừa cấu hình. Theo nguyên tắc chung, nếu một directive hợp lệ ở nhiều cấp độ context lồng nhau, thì giá trị được khai báo ở context bên ngoài sẽ được truyền vào các context con như là giá trị mặc định. Tuy nhiên, context con hoàn toàn có thể ghi đè các giá trị này. Lưu ý rằng nếu directive thuộc dạng mảng, thì việc ghi đè sẽ thay thế toàn bộ giá trị trước đó, chứ không phải bổ sung thêm vào.
Các directive chỉ có thể được sử dụng trong context mà chúng được thiết kế cho. Nếu bạn đặt một directive sai context, Nginx sẽ báo lỗi khi đọc file cấu hình. Tài liệu chính thức của Nginx cung cấp thông tin về context mà mỗi directive có thể được sử dụng, vì vậy đây là nguồn tham khảo rất hữu ích.
Dưới đây, chúng ta sẽ tìm hiểu một số context phổ biến nhất mà bạn thường gặp khi làm việc với Nginx.
Các Context cốt lõi
Nhóm context đầu tiên mà chúng ta sẽ tìm hiểu là các context cốt lõi mà Nginx sử dụng để tạo ra cấu trúc dạng cây phân cấp và tách biệt các khối cấu hình riêng biệt. Đây là những context tạo nên phần khung chính trong cấu hình của Nginx.
Main Context
Context tổng quát nhất là main hoặc global context. Đây là context duy nhất không nằm trong các khối context thông thường có dạng như sau:
/etc/nginx/nginx.conf
# The main context is here, outside any other contexts
. . .
context {
. . .
}
Bất kỳ directive nào nằm hoàn toàn bên ngoài các khối ngoặc nhọn đều thuộc về main context. Lưu ý rằng nếu cấu hình Nginx của bạn được thiết kế theo kiểu mô-đun, tức là các tùy chọn cấu hình được chia ra nhiều file thì một số file có thể chứa các chỉ thị trông như nằm ngoài context có dấu ngoặc, nhưng thực tế khi toàn bộ cấu hình được nạp thì chúng sẽ nằm bên trong một context cụ thể.
Main context là phạm vi cấu hình rộng nhất của Nginx. Nó dùng để thiết lập các thông số ảnh hưởng đến toàn bộ ứng dụng. Mặc dù các chỉ thị trong phần này có thể ảnh hưởng đến các context cấp thấp hơn, nhưng nhiều chỉ thị không thể bị ghi đè ở các cấp thấp hơn.
Một số thiết lập thường được cấu hình trong main context bao gồm: user và group của hệ thống để chạy các worker processes, số lượng worker, và file dùng để lưu ID của tiến trình chính Nginx. File log lỗi mặc định cho toàn bộ ứng dụng cũng có thể được đặt ở cấp này (có thể bị ghi đè ở các context cụ thể hơn).
Events Context
Context “events” nằm trong main context. Nó được dùng để thiết lập các tùy chọn toàn cục liên quan đến cách Nginx xử lý kết nối ở cấp độ tổng thể. Trong toàn bộ cấu hình Nginx, chỉ được khai báo một context events duy nhất.
Context này trong file cấu hình sẽ có dạng như sau, nằm bên ngoài các khối ngoặc nhọn khác:
/etc/nginx/nginx.conf
# main context
events {
# events context
. . .
}
Nginx sử dụng mô hình xử lý kết nối dựa trên sự kiện, vì vậy các chỉ thị được khai báo trong context này sẽ quyết định cách các worker process xử lý các kết nối. Chủ yếu, các chỉ thị trong phần này được dùng để lựa chọn kỹ thuật xử lý kết nối hoặc điều chỉnh cách các phương pháp đó được thực hiện.
Thông thường, phương pháp xử lý kết nối sẽ được Nginx tự động lựa chọn dựa trên giải pháp hiệu quả nhất mà hệ điều hành hiện tại hỗ trợ. Trên các hệ thống Linux, phương pháp epoll
thường là lựa chọn tối ưu nhất.
Ngoài ra, bạn cũng có thể cấu hình số lượng kết nối mà mỗi worker có thể xử lý, việc một worker chỉ nhận một kết nối tại một thời điểm hay nhận toàn bộ các kết nối đang chờ sau khi được thông báo, và liệu các worker có lần lượt thay phiên nhau phản hồi các sự kiện hay không.
HTTP Context
Khai báo context http có lẽ là phần phổ biến nhất khi cấu hình Nginx. Khi sử dụng Nginx như một web server hoặc reverse proxy, phần lớn cấu hình sẽ nằm trong context “http”. Context này sẽ chứa tất cả các chỉ thị và các context con cần thiết để định nghĩa cách chương trình xử lý các kết nối HTTP hoặc HTTPS.
Context http là context “song song” với context events, vì vậy chúng cần được khai báo cùng cấp, không được lồng vào nhau. Cả hai đều là con của main context, ví dụ:
/etc/nginx/nginx.conf
# main context
events {
# events context
. . .
}
http {
# http context
. . .
}
Trong khi các context cấp thấp hơn đi vào chi tiết cụ thể về cách xử lý request, thì các chỉ thị ở cấp http context điều khiển các giá trị mặc định cho tất cả các virtual server được khai báo bên trong. Có rất nhiều chỉ thị có thể được cấu hình tại context này và các cấp thấp hơn, tùy thuộc vào cách bạn muốn áp dụng cơ chế kế thừa cấu hình.
Một số chỉ thị phổ biến mà bạn sẽ gặp bao gồm:
- Thiết lập vị trí mặc định cho log truy cập và log lỗi:
access_log
vàerror_log
- Cấu hình I/O bất đồng bộ cho thao tác file:
aio
,sendfile
, vàdirectio
- Thiết lập trạng thái phản hồi của server khi có lỗi xảy ra:
error_page
- Cấu hình nén dữ liệu:
gzip
vàgzip_disable
- Tinh chỉnh các thiết lập liên quan đến kết nối TCP keep-alive:
keepalive_disable
,keepalive_requests
, vàkeepalive_timeout
- Quy định cách Nginx tối ưu hóa gói tin và system calls:
sendfile
,tcp_nodelay
, vàtcp_nopush
- Thiết lập thư mục gốc tài liệu (document root) và file index ở cấp ứng dụng:
root
vàindex
- Cấu hình các bảng băm (hash table) dùng để lưu trữ các kiểu dữ liệu khác nhau:
_hash_bucket_size
và_hash_max_size
(áp dụng choserver_names
,types
, vàvariables
)
Để biết thêm chi tiết, bạn có thể tham khảo tài liệu chính thức của Nginx.
Server Context
Context “server” được khai báo bên trong context “http”. Đây là ví dụ đầu tiên của một context được lồng bên trong (dùng dấu ngoặc nhọn) và cũng là context đầu tiên cho phép khai báo nhiều lần.
Cấu trúc tổng quát của một context server có thể như sau. Lưu ý rằng các block này nằm bên trong context http:
/etc/nginx/nginx.conf
# main context
http {
# http context
server {
# first server context
}
server {
# second server context
}
}
Bạn có thể khai báo nhiều context server
, vì mỗi khối server sẽ định nghĩa một virtual server cụ thể để xử lý các yêu cầu từ client. Bạn có thể tạo bao nhiêu server block tùy ý, và mỗi block sẽ phục vụ một nhóm kết nối cụ thể.
Đây cũng là context đầu tiên mà Nginx phải sử dụng thuật toán lựa chọn. Mỗi yêu cầu từ client sẽ được xử lý dựa trên cấu hình trong một server context duy nhất, vì vậy Nginx cần quyết định context nào là phù hợp nhất dựa trên thông tin của yêu cầu. Có hai chỉ thị phổ biến dùng để xác định điều này, liên quan đến tên miền:
- listen: chỉ định cặp địa chỉ IP / cổng mà server block này được thiết lập để lắng nghe. Nếu một yêu cầu từ client khớp với các giá trị này, thì block này có thể được chọn để xử lý kết nối.
- server_name: đây là thành phần còn lại dùng để chọn một server block để xử lý. Nếu có nhiều block server có chỉ thị listen giống nhau đủ điều kiện xử lý yêu cầu, thì Nginx sẽ phân tích header “Host” trong yêu cầu và so sánh với giá trị của server_name.
Các chỉ thị trong context server có thể ghi đè nhiều chỉ thị đã được định nghĩa trong context http, bao gồm: logging, thư mục gốc của tài liệu, thiết lập nén, v.v. Ngoài những chỉ thị kế thừa từ http, bạn còn có thể cấu hình:
- các file sẽ được thử trả về cho yêu cầu:
try_files
- chuyển hướng hoặc viết lại URL:
return
,rewrite
- thiết lập biến tùy ý:
set
Location Context
Context tiếp theo mà bạn thường xuyên làm việc là location context. Context này có nhiều điểm tương đồng với server context. Ví dụ: bạn có thể khai báo nhiều location, mỗi location dùng để xử lý một kiểu yêu cầu cụ thể từ client, và Nginx sẽ lựa chọn location phù hợp nhất bằng cách so khớp định nghĩa location với yêu cầu từ client thông qua một thuật toán chọn lọc.
Trong khi các chỉ thị để quyết định chọn block server nào được khai báo trong context server, thì khả năng xử lý một request cụ thể của location lại được xác định trong dòng khai báo location, tức là dòng mở đầu block đó.
Cú pháp tổng quát như sau:
/etc/nginx/nginx.conf
location match_modifier location_match {
. . .
}
Các block location nằm trong context server và không giống như các block server có thể được lồng nhau. Điều này rất hữu ích khi bạn muốn tạo một location tổng quát để bắt một nhóm lưu lượng nhất định, rồi tiếp tục xử lý chi tiết hơn dựa trên các tiêu chí cụ thể bằng cách thêm các block location bên trong:
/etc/nginx/nginx.conf
# main context
server {
# server context
location /match/criteria {
# first location context
}
location /other/criteria {
# second location context
location nested_match {
# first nested location
}
location other_nested {
# second nested location
}
}
}
Trong khi các server context được chọn dựa trên cặp địa chỉ IP/cổng được yêu cầu và tên miền trong header “Host”, thì các block location tiếp tục phân chia việc xử lý request bên trong mỗi server block bằng cách xem xét request URI, tức là phần đường dẫn nằm sau tên miền hoặc IP/cổng trong URL.
Ví dụ, khi client gửi yêu cầu đến http://www.example.com/blog
trên cổng 80: các thành phần http
, www.example.com
và port 80 sẽ được dùng để xác định block server phù hợp. Sau khi server được chọn, phần /blog
(request URI) sẽ được so sánh với các block location đã định nghĩa để xác định context phù hợp hơn nữa để xử lý request.
Nhiều chỉ thị mà bạn thường thấy trong location context cũng có thể xuất hiện ở các cấp cha như server hoặc http. Tuy nhiên, một số chỉ thị mới tại cấp location bao gồm:
alias
: cho phép chỉ định một thư mục nằm ngoài document root.internal
: đánh dấu một location chỉ được truy cập từ bên trong.- Các chỉ thị proxy như proxy_pass, fastcgi_pass, scgi_pass, và uwsgi_pass: chuyển tiếp request đến các server hoặc location khác.
Các context khác
Ngoài các context chính ở trên, vẫn còn một số context khác trong Nginx, tuy ít phổ biến hơn hoặc chỉ dùng trong những tình huống đặc biệt:
split_clients
: được dùng để chia client thành các nhóm dựa trên tỷ lệ phần trăm, thông qua biến. Thường được sử dụng cho A/B testing, bằng cách cung cấp nội dung khác nhau cho từng nhóm client.perl
/perl_set
: cho phép cấu hình các handler Perl cho từng location cụ thể. Chỉ dùng khi muốn xử lý request bằng Perl.map
: dùng để gán giá trị cho một biến, dựa trên giá trị của một biến khác. Nó tạo ra một bảng ánh xạ giữa giá trị đầu vào và kết quả mong muốn.geo
: cũng là một dạng ánh xạ, nhưng dùng để phân loại địa chỉ IP của client. Biến sẽ được gán giá trị tùy theo IP của client kết nối.types
: dùng để ánh xạ giữa các MIME type và phần mở rộng file tương ứng. Thường được Nginx nạp từ filemime.types
vào trongnginx.conf
.charset_map
: cũng là một context ánh xạ, nhưng dùng để chuyển đổi giữa hai bảng mã ký tự. Phần header của context chỉ định hai bảng mã, còn phần thân chứa bảng ánh xạ chi tiết.
Upstream Context
Context upstream được dùng để khai báo và cấu hình các server upstream, tức là các server mà Nginx sẽ chuyển tiếp request đến. Context này định nghĩa một nhóm các server được đặt tên, mà Nginx có thể sử dụng để phân phối tải.
Context upstream phải được đặt bên trong context http, nhưng bên ngoài các block server cụ thể. Cú pháp có dạng như sau:
/etc/nginx/nginx.conf
# main context
http {
# http context
upstream upstream_name {
# upstream context
server proxy_server1;
server proxy_server2;
. . .
}
server {
# server context
}
}
Context upstream sau đó có thể được tham chiếu theo tên bên trong các block server hoặc location để chuyển tiếp các request thuộc một loại nhất định đến nhóm server đã được định nghĩa trước. Khi đó, upstream sẽ sử dụng một thuật toán (mặc định là round-robin) để quyết định chuyển request đến server cụ thể nào trong nhóm. Context này giúp Nginx thực hiện chức năng load balancing khi proxy các requests.
If Context
Context “if” có thể được sử dụng để thực thi các chỉ thị một cách có điều kiện. Tương tự như câu lệnh if trong các ngôn ngữ lập trình truyền thống, directive if trong Nginx sẽ thực thi các chỉ thị bên trong nó nếu điều kiện kiểm tra trả về “true”.
Context if trong Nginx được cung cấp bởi rewrite module, và mục đích chính của nó là phục vụ cho việc viết lại URL. Do Nginx đã có rất nhiều chỉ thị chuyên biệt khác để kiểm tra điều kiện trong request, nên if
không nên dùng để xử lý điều kiện một cách tổng quát. Đây là một lưu ý rất quan trọng đến mức cộng đồng Nginx đã tạo hẳn một trang cảnh báo có tên “if is evil”.
Vấn đề là thứ tự xử lý trong Nginx rất dễ gây ra kết quả không như mong đợi khi sử dụng if. Các chỉ thị duy nhất được coi là an toàn khi dùng trong block if là return
và rewrite
cũng chính là hai chỉ thị được tạo ra để dùng trong context này. Ngoài ra, nếu bạn dùng if trong cùng một context với try_files
, thì chỉ thị try_files sẽ trở nên vô hiệu.
Thông thường, if được dùng để xác định xem có cần rewrite hoặc return không. Các block if này thường nằm trong các block location, với cấu trúc phổ biến như sau:
/etc/nginx/nginx.conf
# main context
http {
# http context
server {
# server context
location location_match {
# location context
if (test_condition) {
# if context
}
}
}
}
Ngữ cảnh limit_except
Ngữ cảnh limit_except
được dùng để giới hạn việc sử dụng một số phương thức HTTP nhất định trong một ngữ cảnh location. Ví dụ, nếu chỉ một số client được phép truy cập nội dung bằng phương thức POST, trong khi tất cả mọi người đều có quyền đọc nội dung, bạn có thể dùng một khối limit_except
để xác định yêu cầu này.
Ví dụ trên sẽ giống như sau:
/etc/nginx/nginx.conf
. . .
# server or location context
location /restricted-write {
# location context
limit_except GET HEAD {
# limit_except context
allow 192.168.1.1/24;
deny all;
}
}
Các chỉ thị bên trong ngữ cảnh này (dùng để giới hạn quyền truy cập) sẽ được áp dụng khi gặp bất kỳ phương thức HTTP nào ngoại trừ các phương thức được liệt kê ở phần đầu của khối limit_except. Kết quả của ví dụ trên là: bất kỳ client nào cũng có thể sử dụng các phương thức GET và HEAD, nhưng chỉ các client đến từ subnet 192.168.1.1/24
mới được phép sử dụng các phương thức khác.
Các quy tắc chung khi làm việc với ngữ cảnh trong Nginx
Giờ bạn đã có cái nhìn tổng quan về các ngữ cảnh phổ biến khi làm việc với cấu hình Nginx, hãy cùng tìm hiểu một số nguyên tắc nên tuân theo khi xử lý các ngữ cảnh này.
Khai báo chỉ thị ở ngữ cảnh cao nhất có thể
Nhiều chỉ thị có thể hợp lệ trong nhiều hơn một ngữ cảnh. Ví dụ, có khá nhiều chỉ thị có thể được khai báo trong các ngữ cảnh http, server, hoặc location. Điều này mang lại sự linh hoạt cho việc cấu hình.
Quy tắc chung là: bạn nên khai báo chỉ thị tại ngữ cảnh cao nhất mà nó có thể áp dụng, và chỉ ghi đè ở các ngữ cảnh thấp hơn khi cần thiết. Điều này khả thi nhờ vào mô hình kế thừa mà Nginx áp dụng.
Có nhiều lý do để sử dụng chiến lược này:
Trước hết, việc khai báo ở cấp cao sẽ giúp tránh lặp lại không cần thiết giữa các khối ngữ cảnh sibling. Ví dụ, trong trường hợp bên dưới, mỗi khối location đều khai báo cùng một document root:
/etc/nginx/nginx.conf
http {
server {
location / {
root /var/www/html;
. . .
}
location /another {
root /var/www/html;
. . .
}
}
}
Bạn có thể di chuyển chỉ thị root ra ngoài khối location và đặt nó ở khối server, hoặc thậm chí là ở khối http, như sau:
http {
root /var/www/html;
server {
location / {
. . .
}
location /another {
. . .
}
}
}
Phần lớn thời gian, cấp server sẽ là phù hợp nhất, nhưng khai báo ở cấp cao hơn cũng có những lợi thế. Việc này không chỉ giúp bạn đặt chỉ thị ở ít nơi hơn mà còn cho phép bạn lan truyền giá trị mặc định xuống tất cả các phần tử con, tránh tình huống gặp lỗi do quên khai báo chỉ thị ở cấp thấp hơn. Điều này đặc biệt quan trọng trong các cấu hình dài. Khai báo ở cấp cao cung cấp một giá trị mặc định tốt.
Sử dụng nhiều khối ngang cấp thay vì logic “if” để xử lý
Khi bạn muốn xử lý yêu cầu theo cách khác nhau tùy thuộc vào một số thông tin từ yêu cầu của client, nhiều người dùng thường nghĩ đến việc dùng ngữ cảnh “if” để phân nhánh xử lý. Tuy nhiên, có một số vấn đề với cách làm này như đã đề cập trước đó.
Lý do đầu tiên là chỉ thị “if” thường cho ra kết quả không như kỳ vọng của quản trị viên. Mặc dù đầu ra luôn nhất quán với cùng một đầu vào, cách Nginx diễn giải môi trường có thể khác với giả định trừ khi bạn thử nghiệm kỹ lưỡng.
Lý do thứ hai là đã có các chỉ thị tối ưu, được tạo ra chuyên biệt để xử lý các tình huống như vậy. Nginx đã có sẵn các thuật toán lựa chọn được tài liệu hóa rõ ràng để chọn các khối server và location. Nếu có thể, tốt nhất là bạn nên phân chia cấu hình thành các khối riêng biệt để Nginx có thể sử dụng thuật toán lựa chọn của nó để xử lý logic.
Ví dụ, thay vì dựa vào rewrite để đưa một yêu cầu do người dùng gửi thành định dạng bạn mong muốn, bạn nên thiết lập hai khối xử lý yêu cầu: một khối cho phương thức chính xác mà bạn mong muốn, và một khối khác bắt các yêu cầu không đúng định dạng rồi chuyển hướng (và có thể là viết lại) về khối đúng.
Kết quả thường sẽ dễ đọc hơn và có hiệu năng cao hơn. Các yêu cầu đúng sẽ không cần xử lý bổ sung, còn các yêu cầu sai có thể chỉ cần chuyển hướng thay vì viết lại, điều này thường có chi phí xử lý thấp hơn.
Kết luận
Đến thời điểm này, bạn đã có cái nhìn rõ ràng về các context phổ biến nhất trong Nginx và các chỉ thị tạo nên các khối định nghĩa chúng.
Hãy luôn tham khảo tài liệu chính thức của Nginx để biết một chỉ thị có thể được đặt ở những ngữ cảnh nào và xác định vị trí phù hợp nhất để đặt chúng. Việc cẩn thận trong quá trình tạo cấu hình sẽ giúp bạn dễ bảo trì hơn và thường cải thiện cả hiệu năng hệ thống.