Cánh cửa trước của app: đón mỗi khách ghé, tự phục vụ file tĩnh, chuyển phần còn lại cho app phía sau — và kết thúc HTTPS ngay tại đây.
← / → để chuyển slide · chuyển EN/VI bên dưới
Mọi request của khách đều chạm nginx trước — trước khi chạm tới app Flask của bạn. App không bao giờ thấy thế giới bên ngoài trực tiếp.
ghé shipper.dev — một địa chỉ, một cánh cửa
cánh cửa trước — mọi request đi qua đây
443 · TLS · static · proxy
một cánh cửa, hai kiểu trả lời: phần nginx tự phục vụ, và phần nginx phải chuyển tiếp
nginx nổi tiếng là "reverse proxy" — nhưng khi deploy thật, nó lặng lẽ làm ba việc cùng lúc.
Phục vụ ảnh, CSS, JS thẳng từ đĩa. App không hề tham gia — vì thế website mới nhanh.
Chuyển tiếp các request động (/api, trang) cho app Flask phía sau và gửi câu trả lời về.
Kết thúc kết nối mã hóa ngay tại cửa: khách nhận HTTPS, app nhận HTTP local thường.
nginx là gì, không phải gì, và một request đi qua nó ra sao.
Cùng một chương trình thường được gọi bằng hai tên. Cả hai đều đúng — và bạn đang nghĩ đến mặt nào sẽ thay đổi cách cấu hình.
nginx được trỏ tới một thư mục (root) và phục vụ mọi thứ trong đó: ảnh, CSS, JS — thậm chí cả website tĩnh.
Hãy tưởng tượng: một cửa hàng bán chính kho hàng của mình.
nginx nhận request, chuyển cho chương trình khác (app Flask của bạn), rồi chuyển trả lời về. Khách không bao giờ biết app tồn tại.
Hãy tưởng tượng: quầy lễ tân chuyển đơn vào bếp.
nginx quyết định từng request — theo đường dẫn — trả lời hay chuyển tiếp.
GET https://shipper.dev/…
khớp đường dẫn của request với các location block
trình duyệt chỉ thấy một địa chỉ, một cổng — phía sau: file tức thì và app được chuyển tiếp, khách không thấy gì
App Flask của bạn với một thư mục static. Phần lớn request của một trang do nginx tự trả lời.
| Request | Ai trả lời | Vì sao |
|---|---|---|
| GET / | App Flask (qua proxy) | Trang được tạo ra — cần Python |
| GET /static/app.css | nginx, trực tiếp | File đã nằm sẵn trên đĩa |
| GET /static/app.js | nginx, trực tiếp | Cũng vậy: file thường, không logic |
| GET /api/orders | App Flask (qua proxy) | Trả lời phụ thuộc người dùng + database |
| GET /favicon.ico | nginx, trực tiếp | File tĩnh — trình duyệt còn cache |
App của bạn là cửa hàng. nginx là quầy lễ tân: đón mọi khách, phát tờ rơi, chuyển đơn vào bếp — mà không để khách đi thẳng vào trong.
nginx được cấu hình bằng các block lồng nhau trong một file text. Một site = một server block = vài directive.
Một config thực tế, đầy đủ cho app Flask của bạn. Đọc từ trên xuống — mỗi dòng có một việc.
server {
listen 80;
server_name shipper.dev;
root /var/www/shipper; # static files live here
location / {
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://127.0.0.1:5000;
}
}
Một server block = một site. Hai location block = toàn bộ sự phân chia tĩnh-động.
nginx có vài trăm directive. Sáu cái này đáp ứng 95% điều bạn sẽ đọc trong config của một site.
Mở một block mô tả một site: cổng, tên, quy tắc của nó.
Site này trả lời ở cổng nào — 80 cho HTTP, 443 cho HTTPS.
Domain của block này — khớp với header Host của từng request.
Thư mục chứa file tĩnh — nơi / được tìm trên đĩa.
Quy tắc cho một tiền tố đường dẫn: từng đường dẫn có thể được xử lý khác nhau.
Nơi các request khớp được chuyển tới — địa chỉ của app.
Dòng listen là nơi site nói nó đứng ở cánh cửa nào. Hai cánh cửa — một cửa có khóa.
Cổng web mặc định. Nhanh và đơn giản, nhưng bất kỳ ai trên đường truyền cũng đọc được toàn bộ cuộc trò chuyện.
listen 80; — trình duyệt giờ cảnh báo "Not secure".
Mặc định mã hóa. nginx kết thúc phiên TLS ngay tại cửa, rồi nói chuyện với app bằng HTTP local thường.
listen 443 ssl; — biểu tượng ổ khóa trên thanh địa chỉ.
Một VPS có thể host hàng chục site. server_name là cách nginx phân biệt chúng.
shipper.dev có server block riêng, shop.example.com và blog.example.com cũng vậy.
Mỗi request mang một header Host; nginx chọn block có server_name khớp với nó.
Block đầu tiên (hoặc default_server) trả lời các request không khớp tên nào.
Dòng quan trọng nhất của một reverse proxy: nó chỉ tới nơi app thực sự sống.
Một location block, một địa chỉ: mọi thứ dưới /api/ được giao cho app Flask.
# everything under /api/ goes to the Flask app
location /api/ {
proxy_pass http://127.0.0.1:5000; # the app runs on port 5000
}
/api/orders tới Flask vẫn là /api/orders — nginx đổi người nhận, không đổi nội dung.
Khi nginx chạy trong cùng mạng compose với Flask, nó tiếp cận app theo tên service — không cần mở port.
services:
web:
build: . # our Flask app
nginx:
image: nginx:1.27
ports:
- "80:80"
- "443:443"
depends_on:
- web
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
Chỉ cổng 80/443 của nginx hướng ra internet. Flask nằm riêng trong mạng.
Ý tưởng proxy_pass giống nhau trong cả hai trường hợp — chỉ địa chỉ của app thay đổi.
nginx chạy trên server; Flask chạy trong container ánh xạ ra cổng host. nginx gõ cửa localhost.
proxy_pass http://127.0.0.1:5000;
nginx và Flask là container trong một mạng riêng. Tên service web tự động trỏ tới app.
proxy_pass http://web:5000;
Theo chân /api/orders từ góc nhìn trình duyệt — và của nginx.
GET https://shipper.dev/api/orders, qua TLS, tới cổng 443.
Đường dẫn bắt đầu bằng /api/, nên location block thứ hai thắng.
Nó mở kết nối tới 127.0.0.1:5000 và chuyển request — giờ là HTTP local thường.
Trả lời của Flask chảy qua nginx về trình duyệt. Khách chưa bao giờ gặp cổng 5000.
Không chỉ là quy ước — phục vụ file tĩnh tại cửa là lợi thế cả về tốc độ lẫn bảo mật.
File CSS là một file. nginx đọc từ đĩa và gửi — không process Python, không database, không cần suy nghĩ.
40 request tĩnh của một trang không hề chạm app.
App có thể ngồi trong mạng riêng. Kẻ tấn công tiếp cận được nginx — cánh cửa — nhưng không bao giờ chạm tới cổng app.
Ít đi một cánh cửa vào tòa nhà.
Biểu tượng ổ khóa trên thanh địa chỉ là một chứng chỉ — và nginx là người trình nó ra.
HTTPS = HTTP + TLS. Chứng chỉ làm ba việc, và trình duyệt kiểm tra đủ ba trước khi hiện ổ khóa.
Mọi thứ giữa trình duyệt và nginx bị xáo trộn — kẻ nghe lén trên đường truyền không đọc được gì.
Chứng chỉ chứng minh site thật sự là shipper.dev — không phải bản sao trên server nào đó.
Không ai lặng lẽ thay đổi dữ liệu được gửi — bắt tay sẽ thất bại.
Cùng block như trước, thêm hai dòng chứng chỉ. nginx giờ nói chuyện bằng TLS tại cửa.
server {
listen 443 ssl;
server_name shipper.dev;
ssl_certificate /etc/letsencrypt/live/shipper.dev/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/shipper.dev/privkey.pem;
location / {
proxy_pass http://127.0.0.1:5000;
}
}
Hai file: fullchain.pem được đưa cho trình duyệt; privkey.pem không bao giờ rời server.
Không ai mua chứng chỉ nữa. Let's Encrypt cấp miễn phí — và config của bạn chỉ cần trỏ tới các file.
certbot yêu cầu bạn phục vụ một file đặc biệt tại shipper.dev; cơ quan chứng thực kiểm tra nó qua internet.
Let's Encrypt cấp chứng chỉ miễn phí — hiệu lực 90 ngày, được tự gia hạn bằng timer.
fullchain.pem + privkey.pem nằm trong /etc/letsencrypt/ — đúng chỗ config trỏ tới.
Gia hạn ghi file mới; reload xong nginx sẽ dùng chúng. Ổ khóa không bao giờ biến mất.
Một lệnh, và ổ khóa là của bạn trong 90 ngày.
Config nginx dễ hỏng và dễ kiểm tra. Ba lệnh đủ cho toàn bộ công việc hằng ngày.
Sửa config, kiểm tra, reload — nginx áp dụng thay đổi mà không làm rớt một kết nối nào.
Phần còn lại hiếm khi dùng. Khi cần, bạn sẽ tìm — năm lệnh này thành phản xạ.
| Mục tiêu | Lệnh |
|---|---|
| Kiểm tra config trước khi áp dụng | nginx -t |
| Áp dụng thay đổi không rớt lưu lượng | systemctl reload nginx |
| Restart khi reload không sửa được | systemctl restart nginx |
| Xem ai đang ghé ngay lúc này | tail -f /var/log/nginx/access.log |
| Xem chuyện gì đã sai | tail -f /var/log/nginx/error.log |
Mọi thứ bạn học trong khóa này giờ nằm sau đúng một cánh cửa: DNS tìm nó, TLS bảo vệ nó, proxy quyết định.
https://shipper.dev
TLS · file tĩnh · proxy — điểm kiểm soát duy nhất
:80 · :443
:5000 · mạng riêng
volume
internet nói chuyện với nginx · nginx nói chuyện với app · mọi thứ ở giữa do bạn kiểm soát
nginx có một hộp công cụ đồ sộ. Bốn thứ sau là thật — nhưng không phải bước tiếp theo của bạn.
Phân tán request ra nhiều instance app bằng block upstream. Một app là đủ cho lúc này.
Hạn chế client lạm dụng bằng quy tắc limit_req — chi tiết ops, không phải mô hình tư duy.
Để nginx lưu lại trả lời của backend để phục vụ lại — mạnh mẽ, nhưng là chủ đề tinh chỉnh.
gzip, header, timeout, tinh chỉnh worker… tài liệu tham khảo đã có; bạn tra khi cần.
Internet gõ cửa. nginx trả lời. Mọi thứ còn lại là cuộc trò chuyện giữa nginx và app — riêng tư, nội bộ, và nằm trong tầm kiểm soát của bạn.
Giờ bạn đã biết cả chuỗi: domain → nginx → Flask → database. Chủ đề tiếp theo ghép tất cả thành một bản deploy duy nhất.