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ụ:
- 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.
- 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.
- Tạo một self-signed certificate cho domain
localhostđể Nginx dùng. - 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: httpsdù bản thân nó chạy HTTP. - Backend nhận
X-Forwarded-Forchứ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 | |
|---|---|
| Input | Một backend HTTP (dùng image traefik/whoami — in mọi header nhận được). Một domain test (localhost). |
| Process | Tạ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. |
| Output | Hệ 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ộ:

📦 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 lab | Concept module | Bài gốc |
|---|---|---|
| Tạo + đọc self-signed cert | Certificate, CN/SAN, chuỗi tin cậy | Bài 02 — Certificate & CA |
Nginx terminate TLS, thấy cipher trong curl -v | TLS 1.3 handshake | Bài 01 — TLS/HTTPS handshake |
proxy_pass + X-Forwarded-* | Reverse proxy, TLS termination | Module 2 bài 08 — Reverse proxy Nginx |
Verify từng chặng bằng curl -v, openssl s_client | Bộ công cụ debug | Bà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
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:
- Tạo cert tự ký bằng
openssl reqmột dòng, nhớ đặtCN=localhostvà thêm SANsubjectAltName=DNS:localhost(browser/curl hiện đại yêu cầu SAN, không chỉ CN). nginx.confcần haiserverblock: một cholisten 443 ssl(proxy vào backend), một cholisten 80(redirect 301 sang https).- Forward headers — nhớ 4 cái:
Host,X-Real-IP,X-Forwarded-For,X-Forwarded-Proto. ThiếuX-Forwarded-Protothì backend không biết request gốc là HTTPS. proxy_passtrỏ tớihttp://backend:80— tênbackendlà service name trong compose, Docker DNS tự phân giải.- Khi
curlcert tự ký sẽ báo lỗi verify — dùngcurl -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
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:
- Bật HTTP/2: đổi
listen 443 ssl;thànhlisten 443 ssl;kèmhttp2 on;(Nginx 1.25+). Verify bằngcurl -v --http2— dòng request sẽ làGET / HTTP/2. Liên hệ module 2 bài 04 — HTTP/2 multiplexing. - Thêm rate limiting: dùng
limit_req_zone+limit_req(bài 08 mục 5). Bắn 30 request nhanh bằngfor i in $(seq 30); do curl -sk https://localhost/ -o /dev/null -w "%{http_code}\n"; donevà quan sát429 Too Many Requestsxuất hiện. - Routing nhiều backend: thêm service
backend2, tạolocation /api/trỏ tới nó cònlocation /giữ backend cũ — quan sátproxy_passvới/không trailing slash đổi path thế nào. - Bắt gói thật: chạy
sudo tcpdump -i lo -n 'tcp port 443'(hoặc interface docker bridge) trong khicurl, 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 -vvàopenssl s_clientrằ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-Fortruyề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.
Latency vs throughput — vì sao 'thêm server' không luôn nhanh hơn
Bài tiếpModule 3 — Tổng kết & cheat sheet
Thảo luận & góp ý
Hỏi đáp về bài này