TCP, HTTP & Web cho Backend/Mini-challenge — dựng reverse proxy + HTTPS, debug bằng curl
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ộ:

flowchart LR
    C["curl / browser"] -->|"HTTPS :443"| N["Nginx<br/>terminate TLS"]
    N -->|"HTTP :80<br/>+ X-Forwarded-*"| B["backend whoami<br/>(chi noi bo)"]
    H["curl http :80"] -.->|"301 redirect"| N

📦 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:

# 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:

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:

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

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):

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):

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:

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:

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:

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 03bài 04 mô tả.

✨ Đ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 -vopenssl 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 đáng gửi cho bạn học cùng?

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

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

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

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên

Bài tiếp theo

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