TCP, HTTP & Web cho Backend
28/29
Bài 28 / 29~35 phútTLS & Công cụ DebugMiễn phí… lượt xem

Mini-challenge — dựng reverse proxy + HTTPS, debug bằng curl

Tự dựng Nginx terminate TLS trước một app HTTP bằng Docker Compose, tạo cert, rồi verify từng chặng bằng curl -v: TLS handshake, X-Forwarded headers, routing. Lab tổng hợp cả module TLS + công cụ debug.

Đây là lab tổng hợp của module: bạn ghép TLS handshake (bài 01), certificate (bài 02), reverse proxy (module 2 bài 08) và bộ công cụ debug (bài 03) thành một hệ thống chạy thật. Không có concept mới — chỉ có ráp các mảnh lại và nhìn tận mắt điều từng bài mô tả.

Mục tiêu: dựng một app HTTP thuần (không biết gì về TLS), đặt Nginx phía trước để terminate HTTPS, rồi dùng curl -v chứng minh từng tính chất — TLS kết thúc tại Nginx, backend nhận X-Forwarded-Proto: https, và routing theo path hoạt động.

🎯 Đề bài

Bạn có một backend đơn giản chỉ nói HTTP (in ra các header nó nhận được). Nhiệm vụ:

  1. Chạy backend đó trong một container, chỉ expose HTTP trên mạng nội bộ — không bao giờ lộ ra ngoài.
  2. Dựng Nginx ở phía trước: lắng nghe HTTPS trên cổng 443, terminate TLS, forward request vào backend qua HTTP.
  3. Tạo một self-signed certificate cho domain localhost để Nginx dùng.
  4. Dùng curl -v để chứng minh 4 điều:
    • TLS handshake xảy ra tại Nginx (thấy cipher, cert trong output).
    • Backend nhận X-Forwarded-Proto: https dù bản thân nó chạy HTTP.
    • Backend nhận X-Forwarded-For chứa IP client.
    • Request HTTP cổng 80 bị redirect sang HTTPS (301).

Hoàn thành = chạy được curl -v https://localhost/ và đọc ra cả 4 bằng chứng trên.

🔍 Input → Process → Output

Nội dung
InputMột backend HTTP (dùng image traefik/whoami — in mọi header nhận được). Một domain test (localhost).
ProcessTạo cert tự ký → viết nginx.conf (TLS termination + proxy_pass + forward headers + redirect 80→443) → docker-compose up → verify từng chặng bằng curl/openssl.
OutputHệ thống 2 container: client gọi https://localhost → Nginx (terminate TLS) → backend HTTP. curl -v đọc ra cipher, cert, và các X-Forwarded-* header.

Topology bạn sẽ dựng — TLS kết thúc tại Nginx, backend chỉ thấy HTTP nội bộ:

Mô hình Reverse Proxy Terminate TLS trong Lab 06

📦 Concept mapping — lab này tổng hợp gì

Mỗi bước chạm vào một learning outcome của module:

Bước trong labConcept moduleBài gốc
Tạo + đọc self-signed certCertificate, CN/SAN, chuỗi tin cậyBài 02 — Certificate & CA
Nginx terminate TLS, thấy cipher trong curl -vTLS 1.3 handshakeBài 01 — TLS/HTTPS handshake
proxy_pass + X-Forwarded-*Reverse proxy, TLS terminationModule 2 bài 08 — Reverse proxy Nginx
Verify từng chặng bằng curl -v, openssl s_clientBộ công cụ debugBài 03 — Công cụ debug mạng

▶️ Starter — cấu trúc thư mục

Tạo thư mục lab với bố cục sau. Bạn sẽ điền nginx.conf ở phần lời giải:

reverse-proxy-lab/
├── docker-compose.yml
├── nginx.conf
└── certs/
    ├── localhost.crt   (tao o buoc 1)
    └── localhost.key

docker-compose.yml khởi đầu — backend whoami chạy HTTP nội bộ, Nginx mở cổng 443 và 80:

YAML
# docker-compose.yml
services:
  backend:
    image: traefik/whoami
    # Khong map port ra host -- chi truy cap noi bo qua network docker
    expose:
      - "80"

  nginx:
    image: nginx:1.27-alpine
    ports:
      - "443:443"
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - backend
📌 Vì sao backend dùng expose chứ không ports

expose chỉ mở port trong mạng Docker nội bộ — các container khác gọi được nhưng host và internet thì không. ports mới map ra ngoài. Đây chính là mô hình production: backend ẩn sau proxy, không bao giờ lộ trực tiếp. Client buộc phải đi qua Nginx.

💡 Gợi ý

Nếu muốn tự làm trước khi xem lời giải, đây là các mảnh cần ghép:

  1. Tạo cert tự ký bằng openssl req một dòng, nhớ đặt CN=localhost và thêm SAN subjectAltName=DNS:localhost (browser/curl hiện đại yêu cầu SAN, không chỉ CN).
  2. nginx.conf cần hai server block: một cho listen 443 ssl (proxy vào backend), một cho listen 80 (redirect 301 sang https).
  3. Forward headers — nhớ 4 cái: Host, X-Real-IP, X-Forwarded-For, X-Forwarded-Proto. Thiếu X-Forwarded-Proto thì backend không biết request gốc là HTTPS.
  4. proxy_pass trỏ tới http://backend:80 — tên backend là service name trong compose, Docker DNS tự phân giải.
  5. Khi curl cert tự ký sẽ báo lỗi verify — dùng curl -k (bỏ qua verify) hoặc --cacert ./certs/localhost.crt (verify đúng cách).

✅ Lời giải

Bước 1 — Tạo self-signed certificate

Một lệnh openssl tạo cả key và cert, kèm SAN cho localhost:

Bash
mkdir -p certs
openssl req -x509 -newkey rsa:2048 -nodes \
  -keyout certs/localhost.key \
  -out certs/localhost.crt \
  -days 365 \
  -subj "/CN=localhost" \
  -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"

Giải thích flag: -x509 tạo cert tự ký (không phải CSR), -newkey rsa:2048 sinh key mới 2048-bit, -nodes không mã hoá key (khỏi nhập passphrase), -addext subjectAltName thêm SAN — bắt buộc với client hiện đại. Kiểm tra cert vừa tạo:

Bash
openssl x509 -in certs/localhost.crt -noout -subject -ext subjectAltName
# subject=CN=localhost
# X509v3 Subject Alternative Name: DNS:localhost, IP Address:127.0.0.1

Bước 2 — Viết nginx.conf

NGINX
# nginx.conf -- reverse proxy, TLS termination + redirect 80->443

upstream backend_app {
    server backend:80;          # ten service trong docker-compose
}

# HTTPS server -- terminate TLS, forward HTTP vao backend
server {
    listen 443 ssl;
    server_name localhost;

    ssl_certificate     /etc/nginx/certs/localhost.crt;
    ssl_certificate_key /etc/nginx/certs/localhost.key;
    ssl_protocols       TLSv1.2 TLSv1.3;

    location / {
        proxy_pass         http://backend_app;
        proxy_set_header   Host              $host;
        proxy_set_header   X-Real-IP         $remote_addr;
        proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;   # se la "https"
    }
}

# HTTP server -- redirect tat ca sang HTTPS
server {
    listen 80;
    server_name localhost;
    return 301 https://$host$request_uri;
}

Bước 3 — Khởi động và verify từng chặng

Bash
docker compose up -d
docker compose ps          # ca 2 container Up

Bằng chứng 1 — TLS handshake tại Nginx. Dùng curl -v (cert tự ký nên thêm --cacert):

Bash
curl -v --cacert certs/localhost.crt https://localhost/ 2>&1 | grep -E "SSL|TLS|subject|cipher"
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* Server certificate:
*  subject: CN=localhost
*  subjectAltName: host "localhost" matched cert's "localhost"

TLS được terminate tại Nginx — cipher và cert hiện trong output. Có thể soi sâu hơn bằng openssl s_client (công cụ bài 03):

Bash
openssl s_client -connect localhost:443 -servername localhost </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -dates

Bằng chứng 2 + 3 — Backend nhận X-Forwarded headers. Image whoami in mọi header nó nhận:

Bash
curl -s --cacert certs/localhost.crt https://localhost/ | grep -i "x-forwarded\|x-real"
X-Forwarded-For: 172.18.0.1
X-Forwarded-Proto: https
X-Real-Ip: 172.18.0.1

Đây là bằng chứng then chốt: backend chạy HTTP thuần nhưng biết request gốc là HTTPS nhờ X-Forwarded-Proto: https. Nginx đã giải mã TLS và truyền thông tin qua header — đúng như bài 08 mô tả.

Bằng chứng 4 — HTTP cổng 80 redirect sang HTTPS:

Bash
curl -sI http://localhost/ | head -3
HTTP/1.1 301 Moved Permanently
Location: https://localhost/

Bốn bằng chứng đủ — lab hoàn thành. Dọn dẹp:

Bash
docker compose down
⚠️ Lỗi thường gặp khi chạy lab

curl báo "SSL certificate problem: self-signed": cert tự ký không có CA tin cậy ký — dùng --cacert certs/localhost.crt để verify đúng, hoặc -k để bỏ qua (chỉ dùng khi test). 502 Bad Gateway: Nginx không kết nối được backend — kiểm tra tên service trong proxy_pass khớp docker-compose.yml, và backend đã Up chưa (docker compose logs backend). Backend không thấy X-Forwarded-Proto: thiếu dòng proxy_set_header X-Forwarded-Proto $scheme.

🎓 Mở rộng

Khi đã chạy được lab cơ bản, thử nâng cấp:

  1. Bật HTTP/2: đổi listen 443 ssl; thành listen 443 ssl; kèm http2 on; (Nginx 1.25+). Verify bằng curl -v --http2 — dòng request sẽ là GET / HTTP/2. Liên hệ module 2 bài 04 — HTTP/2 multiplexing.
  2. Thêm rate limiting: dùng limit_req_zone + limit_req (bài 08 mục 5). Bắn 30 request nhanh bằng for i in $(seq 30); do curl -sk https://localhost/ -o /dev/null -w "%{http_code}\n"; done và quan sát 429 Too Many Requests xuất hiện.
  3. Routing nhiều backend: thêm service backend2, tạo location /api/ trỏ tới nó còn location / giữ backend cũ — quan sát proxy_pass với/không trailing slash đổi path thế nào.
  4. Bắt gói thật: chạy sudo tcpdump -i lo -n 'tcp port 443' (hoặc interface docker bridge) trong khi curl, xem 3-way handshake [S]/[S.]/[.] rồi các record TLS — đúng những gì bài 03 và bài 04 mô tả.

Thử ngẫmbạn thêm limit_req (bước 2) nhưng chỉ gắn nó vào block listen 443, không gắn vào block listen 80 chuyên redirect. Bỏ sót đó có mở ra lỗ hổng nào không?

✨ Điều bạn vừa làm được

  • Dựng được một hệ thống reverse proxy + TLS termination chạy thật bằng Docker Compose — kiến trúc giống mọi deployment production.
  • Tạo và đọc một self-signed certificate với SAN bằng openssl — hiểu vì sao client hiện đại cần SAN, không chỉ CN.
  • Chứng minh bằng curl -v và openssl s_client rằng TLS kết thúc tại Nginx còn backend chạy HTTP thuần — ráp lý thuyết bài 01/02/08 thành quan sát cụ thể.
  • Xác nhận X-Forwarded-Proto/X-Forwarded-For truyền thông tin client/scheme vào backend — hiểu vì sao app sau proxy cần đọc các header này để tạo redirect và logging đúng.
  • Dùng đúng công cụ debug (bài 03) để verify từng chặng độc lập — kỹ năng chẩn đoán bạn sẽ dùng mỗi khi một hệ thống có proxy "không chạy".

Bài tiếp theo: Module 3 — Tổng kết & cheat sheet

Bài này có giúp bạn hiểu bản chất không?

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Thảo luận & góp ý

Hỏi đáp về bài này

Bài tiếp theo

Module 3 — Tổng kết & cheat sheet