→ From Coder to Shipper
From Coder to Shipper · DevOps

nginx — reverse proxy và web server

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

Toàn cảnh

Ai trả lời tiếng gõ cửa

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.

🌐 Trình duyệt

ghé shipper.dev — một địa chỉ, một cánh cửa

▶

🚪 nginx

cánh cửa trước — mọi request đi qua đây

443 · TLS · static · proxy

▶
📄 /static/app.css — phục vụ từ đĩa, xong
⚙️ /api/orders — chuyển tới Flask :5000

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

Toàn cảnh · ba công việc

Ba việc, một process

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.

Web server tĩnh

Phục vụ ảnh, CSS, JS thẳng từ đĩa. App không hề tham gia — vì thế website mới nhanh.

Reverse proxy

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 TLS

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.

01
Phần 1

Mô hình tư duy

nginx là gì, không phải gì, và một request đi qua nó ra sao.

Mô hình · hai cái tên

Hai mặt, một process

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.

Web server — sở hữu file

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.

Reverse proxy — không sở hữu gì

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.

Mô hình · hãy hình dung

Giải phẫu một request

nginx quyết định từng request — theo đường dẫn — trả lời hay chuyển tiếp.

🌐 Trình duyệt

GET https://shipper.dev/…

▶

🚪 nginx :443

khớp đường dẫn của request với các location block

▶
📄 /static/app.css
phục vụ từ đĩa — app không bao giờ tỉnh dậy
⚙️ /api/orders
proxy_pass → Flask :5000 → trả lời chảy ngược về

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ì

Mô hình · sự phân chia

Ai trả lời cái 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.

RequestAi trả lờiVì sao
GET /App Flask (qua proxy)Trang được tạo ra — cần Python
GET /static/app.cssnginx, trực tiếpFile đã nằm sẵn trên đĩa
GET /static/app.jsnginx, trực tiếpCũng vậy: file thường, không logic
GET /api/ordersApp Flask (qua proxy)Trả lời phụ thuộc người dùng + database
GET /favicon.iconginx, trực tiếpFile 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.
toàn bộ mô hình tư duy trong một câu
02
Phần 2

nginx.conf — bộ quy tắc

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.

Config · file

Cả một site, trong một block

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.

nginx.conf
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.

Config · từ vựng

Sáu directive bạn sẽ gặp mỗi lần

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.

server

Mở một block mô tả một site: cổng, tên, quy tắc của nó.

listen

Site này trả lời ở cổng nào — 80 cho HTTP, 443 cho HTTPS.

server_name

Domain của block này — khớp với header Host của từng request.

root

Thư mục chứa file tĩnh — nơi / được tìm trên đĩa.

location

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.

proxy_pass

Nơi các request khớp được chuyển tới — địa chỉ của app.

Config · các cổng

Cổng 80 vs cổng 443

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.

80 — HTTP thường

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".

443 — HTTPS

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ỉ.

Config · domain

server_name — một máy, nhiều site

Một VPS có thể host hàng chục site. server_name là cách nginx phân biệt chúng.

Một block một domain

shipper.dev có server block riêng, shop.example.com và blog.example.com cũng vậy.

Khớp với Host

Mỗi request mang một header Host; nginx chọn block có server_name khớp với nó.

Mặc định bắt phần còn lại

Block đầu tiên (hoặc default_server) trả lời các request không khớp tên nào.

03
Phần 3

proxy_pass — chuyển tiếp tới app

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.

Proxying · dòng config

Quy tắc chuyển tiếp

Một location block, một địa chỉ: mọi thứ dưới /api/ được giao cho app Flask.

nginx.conf
# 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.

Proxying · docker

Trong Docker, địa chỉ là một cái tên

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.

docker-compose.yml
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.

Proxying · nginx ở đâu

Hai sổ địa chỉ

Ý tưởng proxy_pass giống nhau trong cả hai trường hợp — chỉ địa chỉ của app thay đổi.

nginx trên host

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 trong compose

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;

Proxying · hành trình

Một request, qua proxy

Theo chân /api/orders từ góc nhìn trình duyệt — và của nginx.

  1. 1

    Trình duyệt hỏi

    GET https://shipper.dev/api/orders, qua TLS, tới cổng 443.

  2. 2

    nginx khớp

    Đường dẫn bắt đầu bằng /api/, nên location block thứ hai thắng.

  3. 3

    nginx chuyển tiếp

    Nó mở kết nối tới 127.0.0.1:5000 và chuyển request — giờ là HTTP local thường.

  4. 4

    Trả lời chảy ngược

    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.

Proxying · lợi ích

Vì sao tự phục vụ file tĩnh?

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.

Tốc độ

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.

Bảo mật

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à.

04
Phần 4

HTTPS và biểu tượng ổ khóa

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.

TLS · vì sao

Ổ khóa thực sự có nghĩa gì

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ã hó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ì.

Danh tính

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 đó.

Toàn vẹn

Không ai lặng lẽ thay đổi dữ liệu được gửi — bắt tay sẽ thất bại.

TLS · config

Server block TLS

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.

nginx.conf
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.

TLS · nguồn

Chứng chỉ đến thế nào — Let's Encrypt

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.

  1. 1

    Chứng minh domain

    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.

  2. 2

    Nhận chứng chỉ

    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.

  3. 3

    File trên đĩa

    fullchain.pem + privkey.pem nằm trong /etc/letsencrypt/ — đúng chỗ config trỏ tới.

  4. 4

    Reload để nhận

    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.

TLS · trong terminal

Cấp nó

Một lệnh, và ổ khóa là của bạn trong 90 ngày.

ops@vps — zsh
$ sudo certbot --nginx -d shipper.dev
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Congratulations! Your certificate is saved to:
/etc/letsencrypt/live/shipper.dev/
$ sudo nginx -t
nginx: configuration file /etc/nginx/nginx.conf test is successful
90 days of padlock — renewed automatically by a timer
05
Phần 5

Giữ cánh cửa trước chạ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.

Ops · mỗi ngày

Kiểm tra, reload, theo dõi

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.

ops@vps — zsh
$ sudo nginx -t
nginx: configuration file /etc/nginx/nginx.conf test is successful
$ sudo systemctl reload nginx
$ tail -f /var/log/nginx/access.log
1.2.3.4 - - [27/Oct/2026:09:12:44 +0000] "GET /api/orders HTTP/1.1" 200 312
1.2.3.4 - - [27/Oct/2026:09:12:45 +0000] "GET /static/app.css HTTP/1.1" 200 8123
test → reload → watch: the whole daily routine
Ops · bảng ghi nhớ

Năm lệnh, toàn bộ công việc

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êuLệnh
Kiểm tra config trước khi áp dụngnginx -t
Áp dụng thay đổi không rớt lưu lượngsystemctl reload nginx
Restart khi reload không sửa đượcsystemctl restart nginx
Xem ai đang ghé ngay lúc nàytail -f /var/log/nginx/access.log
Xem chuyện gì đã saitail -f /var/log/nginx/error.log
Toàn cảnh · cả chuỗi

Một máy giữa internet và app của bạn

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.

🌐 Trình duyệt

https://shipper.dev

▶

🚪 nginx

TLS · file tĩnh · proxy — điểm kiểm soát duy nhất

:80 · :443

▶

🐳 App Flask (container)

:5000 · mạng riêng

▶

🗄 Database

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

Toàn cảnh · ngoài phạm vi

Những gì cố tình bỏ qua

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.

Load balancing

Phân tán request ra nhiều instance app bằng block upstream. Một app là đủ cho lúc này.

Rate limiting

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.

Caching (proxy_cache)

Để 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.

Đào sâu directive

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.
góc nhìn của kiến trúc sư

Cửa mở. Lưu lượng chạy.

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.