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ộ:
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 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ả.
✨ Đ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 đá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
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