HTTP là gì? Hướng dẫn cho sysadmin
Tìm hiểu HTTP từ góc nhìn sysadmin: method, status code, header, access log nginx, cache, TLS và điểm khác nhau khi dùng HTTP/3.
HTTP là gì?
HTTP (giao thức truyền siêu văn bản) là tập hợp các quy tắc mà client và web server dùng để yêu cầu và gửi dữ liệu. Client gửi một request gồm method như GET, path như /pricing, phiên bản giao thức, danh sách header và đôi khi có body. Server trả lời bằng status code như 200, sau đó là các header của server và thường là một body. Mỗi lần mở trang và mỗi lần gọi API (application programming interface) trên server của bạn đều là một lần trao đổi như vậy, được lặp lại.
HTTP tự nó không lưu trạng thái. Server không nhớ bạn đã yêu cầu gì một giây trước, vì vậy mọi thứ cần hoạt động như bộ nhớ, chẳng hạn login session, đều được truyền trong header của từng request. Đặc điểm này giải thích phần lớn nội dung bên dưới: cache hoàn toàn do header điều khiển, và load balancer có thể gửi request tiếp theo của bạn đến một backend khác mà không làm gián đoạn hoạt động.
Toàn bộ nội dung bên dưới mô tả mô hình đó từ phía server, trong access log và trong cấu hình nginx của bạn.
Một request và response thô, có chú thích
Đây là một HTTP/1.1 request hoàn chỉnh. Một dòng trống kết thúc phần headers, và bất kỳ nội dung nào sau dòng đó là body. Một GET thường không có body.
GET /pricing HTTP/1.1
Host: example.com
User-Agent: curl/8.5.0
Accept: */*
Accept-Encoding: gzipGETlà method, cho biết bạn muốn thực hiện hành động gì.GETdùng để đọc,POSTgửi dữ liệu,PUTthay thế,DELETExóa,HEADyêu cầu headers của mộtGETmà không lấy body./pricinglà đường dẫn. Hostname không nằm trong dòng request, đó là lý do header tiếp theo tồn tại.HTTP/1.1là phiên bản giao thức mà client đang sử dụng.Host: example.comchỉ định trang web mà client muốn truy cập. HTTP/1.1 bắt buộc có nó, vì vậy nginx sẽ trả về400 Bad Requestcho các request không có header này.- Các phần còn lại là tùy chọn.
Accept-Encoding: gzipcho biết client có thể giải nén, nên server được phép nén body.
Response có cấu trúc tương tự với dòng trạng thái ở trên cùng.
HTTP/1.1 200 OK
Date: Thu, 06 Aug 2026 09:12:44 GMT
Server: nginx
Content-Type: text/html; charset=utf-8
Content-Length: 5310
Cache-Control: public, max-age=300
<!doctype html>...200 OKlà mã trạng thái cùng với cụm từ mô tả. Mã trạng thái mới là thứ quan trọng. Cụm từ chỉ là phần trang trí và client sẽ bỏ qua nó.Content-Typecho client biết cách xử lý các byte theo sau.Content-Lengthlà kích thước body tính bằng byte, để client biết nơi kết thúc body. Khi không biết trước kích thước, server sẽ gửiTransfer-Encoding: chunkedthay thế và đánh dấu kết thúc bằng một chunk có độ dài bằng 0.Cache-Controlcho trình duyệt và bất kỳ cache trung gian nào biết thời gian phản hồi này được coi là mới. Sau khi vượt quámax-agegiây, nó trở nên cũ (stale), điều này không có nghĩa là bị xóa: cache có thể giữ một bản sao cũ và xác thực lại với server của bạn thay vì tải lại toàn bộ body.- Dòng trống sau các headers tách biệt chúng khỏi body, áp dụng cho cả hai chiều.
Tên header không phân biệt chữ hoa chữ thường, và mỗi dòng kết thúc bằng carriage return theo sau là line feed thay vì chỉ một newline đơn thuần. Bạn sẽ không gõ những thứ này bằng tay, nhưng bạn sẽ gặp chúng trong các bản packet capture.
Để theo dõi một cặp request/response thực tế, hãy chạy lệnh này với một trang web bạn sở hữu:
curl -sS -o /dev/null -D - https://example.com/-D - ghi các response headers ra terminal của bạn và -o /dev/null loại bỏ phần body. Hãy ưu tiên dùng cách này thay vì curl -I, vì -I gửi một HEAD request. Một application server xử lý HEAD khác với GET (nhiều server làm như vậy) sẽ hiển thị cho bạn các headers mà không trình duyệt nào nhận được. curl -v in ra cả hai phía, với các dòng request được đánh dấu > và các dòng response được đánh dấu <.
Dòng request trong access log của nginx trông như thế nào
nginx có sẵn format log combined. Định nghĩa của format này là:
log_format combined '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';Một dòng do format này tạo ra:
203.0.113.45 - - [06/Aug/2026:09:12:44 +0000] "GET /pricing HTTP/1.1" 200 5310 "https://example.com/" "Mozilla/5.0 (X11; Linux x86_64) Chrome/127.0.0.0 Safari/537.36"203.0.113.45là$remote_addr, tức địa chỉ đã mở kết nối TCP (transmission control protocol). Khi đi qua proxy, đây là địa chỉ của proxy, không phải của người truy cập, trừ khi bạn đã cấu hình module realip được mô tả ở phần dưới để thay thế giá trị này.-đầu tiên là placeholder cố định.-thứ hai là$remote_user, chỉ được điền khi sử dụng HTTP basic authentication."GET /pricing HTTP/1.1"là$request, toàn bộ request line ban đầu. Với cơ chế escape mặc định của nginx, dấu ngoặc kép, dấu gạch chéo ngược và mọi byte nhỏ hơn 32 hoặc lớn hơn 126 được ghi dưới dạng\xXX. Vì vậy dòng log vẫn phản ánh chính xác nội dung, nhưng không phải lúc nào cũng giống từng byte một.200là status mà server trả về, không phải status mà người truy cập nhận được.5310là$body_bytes_sent, chỉ tính phần body. Response header không được tính, nên giá trị này luôn nhỏ hơn tổng số byte thực tế đã gửi.- Hai field cuối được đặt trong dấu ngoặc kép là
ReferervàUser-Agent. Cả hai đều do client cung cấp, nên có thể chứa bất kỳ nội dung nào.
Dữ liệu rác cũng được ghi log dưới dạng đã escape. Client sử dụng TLS (transport layer security) để kết nối đến cổng plaintext 80 sẽ tạo ra một dòng 400 có request field bắt đầu bằng các byte đã escape như "\x16\x03\x01\x02\x00\x01". \x16 là loại bản ghi handshake của TLS, nên các byte đó là phần đầu của ClientHello, hoàn toàn không phải request line. Server của bạn đang hoạt động đúng. Có thành phần đang gửi HTTPS đến một cổng HTTP.
Hãy thêm $server_protocol vào format log. Nó in ra HTTP/1.1, HTTP/2.0 hoặc HTTP/3.0. Đây là cách nhanh nhất để xác nhận thay đổi protocol thực sự đã có hiệu lực.
Ý nghĩa các mã trạng thái phổ biến khi trang web của bạn trả về chúng
Chữ số đầu tiên là lớp mã, và bạn nên đọc lớp mã trước.
2xx nghĩa là thành công. 200 OK cho một yêu cầu đọc thông thường. 201 Created sau một yêu cầu POST đã tạo ra một đối tượng. 204 No Content cho kết quả thành công nhưng không có nội dung trả về, đây là phản hồi thông thường cho một yêu cầu DELETE.
3xx nghĩa là tìm ở nơi khác. 301 là chuyển hướng vĩnh viễn và trình duyệt sẽ cache rất mạnh, đôi khi cho đến khi người dùng xóa profile của họ, vì vậy một cấu hình 301 trỏ sai hostname sẽ rất khó khắc phục. Hãy dùng 302 khi bạn vẫn đang thử nghiệm chuyển hướng. 304 Not Modified là một kết quả thành công, không phải lỗi: client đã gửi một yêu cầu có điều kiện, hoặc là If-None-Match chứa một ETag (entity tag) mà bạn vẫn nhận diện được, hoặc If-Modified-Since với một ngày mà tài nguyên chưa thay đổi kể từ đó, vì vậy bạn chỉ trả về header mà không có body. Một log đầy các mã 304 nghĩa là cơ chế cache đang hoạt động tốt.
4xx nghĩa là yêu cầu không hợp lệ. 400 Bad Request là đầu vào sai định dạng. 401 Unauthorized thực sự có nghĩa là chưa xác thực, và nó phải đi kèm với header WWW-Authenticate chỉ định phương thức xác thực. 403 Forbidden nghĩa là yêu cầu đã được hiểu nhưng bị từ chối. 404 Not Found là một đường dẫn không tồn tại, hoặc một đường dẫn mà server không muốn thừa nhận là tồn tại, vì một số ứng dụng trả về 404 thay vì 403 để giấu tài nguyên khỏi các client không có quyền truy cập. 405 Method Not Allowed là đường dẫn đúng nhưng sai phương thức, đây là kết quả khi thực hiện POST vào một vị trí file tĩnh. 413 là khi body lớn hơn giá trị client_max_body_size của nginx, mặc định là 1 megabyte, và log lỗi sẽ xác nhận điều này bằng client intended to send too large body.
Một mã 403 trên file tĩnh hầu như luôn liên quan đến filesystem thay vì quy tắc HTTP. Hãy đọc /var/log/nginx/error.log trước khi thay đổi bất kỳ cấu hình nào. open() "/srv/site/index.html" failed (13: Permission denied) nghĩa là user chạy nginx không thể đọc file, thường là do thư mục cha thiếu quyền thực thi (execute) cho người dùng khác. directory index of "/srv/site/" is forbidden nghĩa là đường dẫn trỏ đến một thư mục không có file index trong khi autoindex đang tắt.
5xx nghĩa là lỗi từ phía bạn. 500 là lỗi server chung chung, và trên trang web của bạn, nó thường chỉ ra một lỗi chưa được xử lý trong ứng dụng. 502 Bad Gateway nghĩa là nginx không thể nhận phản hồi hợp lệ từ upstream, và log lỗi sẽ chỉ rõ nguyên nhân: connect() failed (111: Connection refused) while connecting to upstream nghĩa là không có tiến trình nào đang lắng nghe tại địa chỉ trong proxy_pass. 504 Gateway Timeout nghĩa là nginx đã từ bỏ việc chờ đợi upstream. Trường hợp thông thường là upstream đã chấp nhận kết nối nhưng không phản hồi trong vòng proxy_read_timeout, mặc định là 60 giây, điều này được log ghi lại là upstream timed out (110: Connection timed out) while reading response header from upstream. Một nỗ lực kết nối không hoàn tất trong vòng proxy_connect_timeout, cũng mặc định là 60 giây, sẽ kết thúc bằng 504, và dòng log sau đó sẽ kết thúc bằng while connecting to upstream. 503 Service Unavailable nghĩa là server tạm thời không thể xử lý yêu cầu, thường do quá tải hoặc đang bảo trì định kỳ. Lưu ý rằng bộ giới hạn tốc độ (rate limiter) của nginx trả về 503, vì limit_req_status mặc định là 503. Nếu bạn đang tìm kiếm 429 Too Many Requests trong log mà chỉ thấy 503, thì đó là lý do. Hãy thiết lập limit_req_status 429; để nhận được mã chính xác.
Các header quan trọng khi chạy server
Host chọn site. Một địa chỉ IP có thể phục vụ hàng trăm hostname, và nginx so sánh Host với server_name để quyết định block server nào sẽ xử lý. Nếu không có giá trị nào khớp, nginx dùng default server. Đây là block đầu tiên lắng nghe trên địa chỉ và cổng đó, trừ khi block khác được đánh dấu default_server. Nếu virtual host mới trả về nhầm site, gần như luôn là do tên không khớp nên request rơi xuống default server. Kiểm tra mà không cần thay đổi DNS:
curl -sS -o /dev/null -D - -H 'Host: app.example.com' http://127.0.0.1/User-Agent là phần tự mô tả do client gửi và có thể chứa văn bản tùy ý. Hãy dùng nó như một gợi ý khi đọc log. Không bao giờ dùng nó làm cơ chế kiểm soát, vì client muốn giả mạo giá trị này thì chỉ cần tự đặt lại. Vì vậy, chặn scraper theo User-Agent chỉ lọc được những client lịch sự.
Content-Type quyết định cách diễn giải các byte: application/json cho API request, text/html; charset=utf-8 cho page. nginx ánh xạ phần mở rộng file sang kiểu dữ liệu bằng /etc/nginx/mime.types, còn nginx.conf được đóng gói sẵn sẽ đặt default_type application/octet-stream;. Vì vậy, file có phần mở rộng mà nginx không biết sẽ được cung cấp dưới dạng download thay vì được render. Biểu hiện thường thấy là page tải xong nhưng không có style, trong khi console của browser in ra Refused to apply style from ... because its MIME type ('text/plain') is not a supported stylesheet MIME type. MIME ở đây là viết tắt của multipurpose internet mail extensions, tên của scheme tạo ra các chuỗi kiểu dữ liệu đó.
Cache-Control là cách kiểm soát mọi cache nằm giữa server và người đọc. public, max-age=31536000, immutable phù hợp với asset có content hash trong filename, vì tên file sẽ thay đổi khi nội dung thay đổi. no-store phải dùng cho mọi nội dung riêng của từng user, vì shared cache giữ lại một page sau khi đăng nhập sẽ gửi page đó cho người tiếp theo yêu cầu cùng URL. private là thiết lập trung gian: browser có thể giữ lại, còn shared cache thì không.
X-Forwarded-For tồn tại vì proxy che giấu visitor. Sau khi request đi qua reverse proxy, $remote_addr là địa chỉ của proxy, nên log, geolocation và rate limiting của bạn đều chỉ thấy một client cho đến khi khôi phục địa chỉ ban đầu. Proxy phải chuyển tiếp địa chỉ ban đầu:
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;Sau đó, server nhận request phải được cấu hình để tin địa chỉ này và phải xác định chính xác nguồn được phép tin:
set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;Hai directive này thuộc realip module của nginx. Module thay $remote_addr bằng địa chỉ được forward và lưu địa chỉ của chính connection trong $realip_remote_addr. Nếu tự compile nginx, hãy thêm --with-http_realip_module vì module này không được build mặc định.
Chỉ liệt kê các range do bạn kiểm soát. X-Forwarded-For là văn bản thuần mà client nào cũng có thể gửi, nên set_real_ip_from 0.0.0.0/0; cho phép visitor tự chọn địa chỉ được ghi vào log và địa chỉ được tính cho rate limiter.
X-Forwarded-Proto ngăn một lỗi cụ thể và rất phổ biến. Proxy của bạn terminate TLS rồi forward request đến application qua HTTP thuần. Application thấy request thuần HTTP, quyết định visitor phải dùng HTTPS và trả về 301 https://example.com/. Browser làm theo, proxy lại terminate TLS rồi tiếp tục forward HTTP thuần, khiến vòng lặp lặp lại cho đến khi browser dừng với lỗi ERR_TOO_MANY_REDIRECTS. Gửi X-Forwarded-Proto: https cho application biết visitor đã dùng HTTPS, nên application không còn redirect nữa.
HTTP/1.1 so với HTTP/2 và HTTP/3: những thay đổi đối với bạn
HTTP/1.1 là giao thức dạng văn bản, và trên thực tế nó xử lý từng yêu cầu một trên mỗi kết nối. Connection: keep-alive cho phép yêu cầu tiếp theo tái sử dụng cùng một kết nối TCP, giúp tiết kiệm chi phí thiết lập, nhưng các phản hồi vẫn trả về theo thứ tự đã được yêu cầu. Giao thức này cũng cho phép pipelining, nơi client gửi nhiều yêu cầu mà không cần chờ phản hồi, nhưng quy tắc thứ tự vẫn áp dụng và không trình duyệt hiện đại nào bật pipelining theo mặc định. Một phản hồi chậm sẽ chặn mọi thứ đang xếp hàng phía sau. Đó là hiện tượng head-of-line blocking, và các trình duyệt khắc phục bằng cách mở nhiều kết nối cùng lúc đến một hostname.
HTTP/2 giữ nguyên các phương thức và mã trạng thái, nhưng thay đổi cách đóng gói thành dạng nhị phân. Nhiều yêu cầu chia sẻ chung một kết nối dưới dạng các stream độc lập, và văn bản header lặp lại được nén lại, điều này rất quan trọng vì một yêu cầu hiện đại chứa rất nhiều header. Kết nối vẫn là TCP, nên một gói tin bị mất sẽ làm đình trệ mọi stream trên kết nối đó cho đến khi gói tin được truyền lại. Hiện tượng head-of-line blocking không biến mất. Nó chỉ chuyển từ tầng HTTP xuống tầng vận chuyển. Server push từng là một phần của HTTP/2 nhưng trên thực tế đã bị loại bỏ, vì Chrome đã ngừng hỗ trợ tính năng này từ năm 2022.
HTTP/3 giữ nguyên ngữ nghĩa và thay thế TCP bằng QUIC, một giao thức vận chuyển xây dựng trên UDP (user datagram protocol). Các stream QUIC độc lập hoàn toàn, nên một gói tin bị mất chỉ làm đình trệ stream chứa nó. TLS 1.3 được tích hợp sẵn vào quá trình bắt tay (handshake) của QUIC thay vì nằm chồng lên trên, nên một kết nối mới cần ít round trip hơn. Có hai hệ quả thực tế: cổng UDP mà server của bạn dùng cho HTTP/3 phải được mở trong mọi firewall trên đường truyền, và bất kỳ mạng nào giới hạn hoặc chặn UDP sẽ đẩy client quay lại HTTP/2. Cổng đó thường là 443, nhưng giao thức cho phép server dùng bất kỳ cổng UDP nào, vì thông báo chỉ dẫn trình duyệt đến HTTP/3 luôn nêu rõ cổng cụ thể.
Những thay đổi cụ thể đối với bạn. Các trình duyệt thường không bắt đầu bằng HTTP/3. Chúng kết nối qua HTTP/2 hoặc HTTP/1.1, thấy header Alt-Svc: h3=":443"; ma=86400 trong phản hồi, và dùng HTTP/3 cho các kết nối sau đó đến host đó. Ngoại lệ là bản ghi HTTPS trong DNS mà giá trị alpn liệt kê h3, cho phép trình duyệt như Firefox mở HTTP/3 ngay từ kết nối đầu tiên. Bản ghi đó là một gợi ý bổ sung, không phải là thay thế, nên header vẫn không phải là trang trí tùy chọn. Nó là cơ chế khám phá chính. Trong nginx, HTTP/2 trở thành một directive riêng từ phiên bản 1.25.1 (http2 on; bên trong block server, thay thế tham số listen ... http2 cũ), và QUIC xuất hiện trong bản mainline 1.25.0, nơi một site HTTP/3 cần listen 443 quic reuseport; cùng với listen 443 ssl; thông thường. Module HTTP/3 không được build mặc định (--with-http_v3_module), và tài liệu của nginx vẫn gọi hỗ trợ này là thử nghiệm.
Các proxy có mức độ hoàn thiện khác nhau, bạn nên kiểm tra lại với phiên bản mình đang chạy. Tính đến tháng 11 năm 2026, Caddy phục vụ HTTP/3 mặc định mà không cần cấu hình. nginx cần listener quic rõ ràng cộng với header Alt-Svc đã mô tả ở trên. Traefik kích hoạt nó cho từng entry point thông qua tùy chọn http3 cụ thể. Nếu bạn terminate TLS tại Traefik phía trước nhiều ứng dụng Docker, phiên bản giao thức mà khách truy cập nhận được sẽ được quyết định tại đó, và chặng từ proxy đến container của bạn thường là HTTP/1.1 thuần túy bất kể trình duyệt đã thương lượng gì.
Hãy xác minh thay vì giả định. curl --http3 -sS -o /dev/null -D - https://example.com/ chỉ hoạt động nếu curl -V liệt kê HTTP3 trong các tính năng của nó, và hầu hết các bản build từ distribution không bao gồm tính năng này. Cách kiểm tra đáng tin cậy là xem log của chính bạn: thêm $server_protocol vào định dạng log và đọc xem các trình duyệt thực tế đang thương lượng những gì. Trước khi làm bất cứ điều gì, hãy xác nhận rằng cổng UDP mà listener quic của bạn sử dụng (443 trong cấu hình trên) thực sự đang mở, vì một firewall chỉ cho phép TCP 443 sẽ khiến HTTP/3 thất bại âm thầm trong khi site vẫn hoạt động qua HTTP/2. Biết được những cổng nào đang mở và lắng nghe trên server Linux của bạn là việc đầu tiên cần kiểm tra.
HTTPS: HTTP là giao thức, TLS là lớp bao
HTTPS không phải là một giao thức riêng. Đây là các request và status code giống HTTP, được truyền bên trong một phiên TLS. Cổng 80 truyền chúng ở dạng không mã hóa, còn cổng 443 truyền chúng đã được mã hóa. TLS handshake hoàn tất trước, sau đó HTTP request mới đi bên trong kênh đã mã hóa. Vì vậy, lỗi certificate không bao giờ đi kèm status code: lỗi xảy ra trước khi một byte HTTP được gửi, nên không có response để đánh số.
Một chi tiết về thứ tự xử lý rất quan trọng trên server host nhiều site. Certificate được chọn bằng SNI (server name indication), một trường trong TLS handshake chứa hostname ở dạng không mã hóa trước khi có bất kỳ HTTP header nào. Vì vậy, server chọn certificate từ SNI trước, rồi chọn virtual host từ header Host sau. Đây là hai lần tra cứu riêng biệt, nhưng thông thường sẽ cho cùng một kết quả. Khi chúng không khớp, browser hiển thị lỗi không khớp tên như NET::ERR_CERT_COMMON_NAME_INVALID và không gửi request nào, vì certificate của default server đã được cung cấp cho một tên mà nó không bao phủ.
Với site public, hãy lấy certificate hợp lệ và để hệ thống tự gia hạn. Certbot với Let's Encrypt trên nginx ghi các đường dẫn certificate vào server block và cài renewal timer giúp bạn. Với hostname mà không có public authority nào có thể xác thực, chẳng hạn tên nội bộ hoặc địa chỉ IP trống trên mạng của bạn, certificate tự ký trên Ubuntu là lựa chọn phù hợp, miễn là bạn chấp nhận việc phải cấu hình mọi client để trust certificate đó.
Khi TLS đã hoạt động, chuyển toàn bộ lưu lượng trên cổng 80 sang cổng 443:
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}Chỉ thêm Strict-Transport-Security khi bạn chắc chắn. Header add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always; yêu cầu browser từ chối HTTP không mã hóa cho hostname đó trong hai năm. Browser thực hiện việc này từ cache của chính nó, nên xóa header sau đó cũng không hoàn tác được. Hãy bắt đầu với max-age trong vài giờ, xác nhận mọi subdomain thực sự dùng HTTPS, rồi mới tăng thời hạn.
FAQ
Sự khác biệt giữa HTTP và HTTPS là gì?
HTTPS là HTTP được truyền bên trong một phiên TLS (transport layer security). Các phương thức và mã trạng thái là giống hệt nhau. Điểm khác biệt là các byte dữ liệu được mã hóa giữa client và thiết bị kết thúc TLS, và cổng mặc định chuyển từ 80 sang 443. Vì quá trình bắt tay TLS hoàn tất trước khi byte HTTP đầu tiên được gửi đi, lỗi chứng chỉ không bao giờ tạo ra mã trạng thái, đó là lý do tại sao cảnh báo chứng chỉ của trình duyệt hiển thị tên lỗi như NET::ERR_CERT_COMMON_NAME_INVALID thay vì một con số như 403.
Tại sao trang web của tôi trả về 502 Bad Gateway?
Một mã 502 từ nginx nghĩa là nginx không thể nhận được phản hồi hợp lệ từ upstream mà nó proxy tới, vì vậy yêu cầu của người dùng là bình thường nhưng thành phần phía sau nginx thì không. Hãy đọc /var/log/nginx/error.log. connect() failed (111: Connection refused) while connecting to upstream nghĩa là không có tiến trình nào đang lắng nghe tại địa chỉ và cổng trong proxy_pass, vì vậy hãy kiểm tra xem ứng dụng có đang chạy và bind đúng vị trí bạn mong đợi hay không. no live upstreams while connecting to upstream nghĩa là mọi server trong khối upstream đã bị đánh dấu là down sau nhiều lần thất bại liên tiếp. Hãy so sánh với 504 Gateway Timeout, lỗi này nghĩa là nginx bị timeout khi chờ upstream, có thể là do kết nối không hoàn tất trong proxy_connect_timeout hoặc không nhận được phản hồi trong proxy_read_timeout.
Tại sao access log của tôi hiển thị cùng một địa chỉ IP cho mọi khách truy cập?
Vì $remote_addr ghi lại địa chỉ đã mở kết nối TCP, và khi đứng sau một reverse proxy hoặc mạng phân phối nội dung (CDN), địa chỉ đó chính là proxy. Địa chỉ thực của khách truy cập nằm trong header X-Forwarded-For. Hãy thiết lập proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; trên proxy, sau đó trên nginx nhận yêu cầu, hãy thiết lập set_real_ip_from thành dải địa chỉ của proxy và real_ip_header X-Forwarded-For;. Chỉ liệt kê các dải địa chỉ mà bạn kiểm soát, vì header đó là văn bản mà bất kỳ client nào cũng có thể gửi, nên việc tin tưởng nó từ toàn bộ Internet sẽ cho phép khách truy cập tùy ý chọn địa chỉ mà bạn ghi log và địa chỉ mà bạn áp dụng rate limit.
Tôi có cần bật HTTP/2 hoặc HTTP/3 không?
HTTP/2 rất đáng để bật, vì nó chỉ là một chỉ thị trên trang web đã có sẵn TLS và nó loại bỏ giới hạn số lượng yêu cầu trên mỗi kết nối vốn làm chậm các trang có nhiều file nhỏ. HTTP/3 mang lại lợi ích nhỏ hơn và ít chắc chắn hơn, đồng thời tiêu tốn của bạn một cổng UDP mở, thường là 443, cộng với một bản build proxy có hỗ trợ QUIC. Hãy nhớ rằng trình duyệt thường chỉ chuyển sang HTTP/3 sau khi chúng thấy header Alt-Svc trong một phản hồi trước đó, hoặc một bản ghi HTTPS DNS liệt kê h3, vì vậy nếu không có một trong hai điều đó, sẽ không có gì thay đổi bất kể dòng listen của bạn ghi gì. Hãy thêm $server_protocol vào định dạng log và đo lường xem khách truy cập thực sự đang thương lượng giao thức nào trước khi dành thời gian cho nó.
Lỗi 403 Forbidden nghĩa là gì khi file vẫn tồn tại?
Trên một trang web tĩnh, lỗi 403 thường là vấn đề quyền hạn trên hệ thống file thay vì quy tắc HTTP. open() ... failed (13: Permission denied) trong /var/log/nginx/error.log nghĩa là user chạy nginx không thể đọc file, thường là do thư mục cha thiếu quyền thực thi (execute) cho người dùng khác (others) chứ không phải do mode của chính file đó bị sai. directory index of ... is forbidden nghĩa là yêu cầu trỏ đến một thư mục không có file index trong khi autoindex đang tắt. Một quy tắc deny rõ ràng trong khối location tương ứng cũng trả về 403, vì vậy hãy đọc khối đó khi error log không hiển thị thông tin gì.