Cấu hình Nginx và Cloudflare làm Reverse Proxy cho WebSocket
Hướng dẫn production cấu hình Nginx và Cloudflare Reverse Proxy cho WebSocket, xử lý timeout, sticky sessions, SSL/TLS termination, IP thật và lỗi 400/502/1006.
ShanverseCập nhật 10 phút đọc— lượt xemĐang tải lượt xem
WebSocket
Nginx
Cloudflare
Kiến trúc cần hình dung đúng
Có hai TLS hop riêng biệt:
Client → Cloudflare Edge: Cloudflare trình bày Edge Certificate cho trình duyệt.
Cloudflare → Nginx Origin: origin nên tiếp tục dùng HTTPS với chứng chỉ hợp lệ và chế độ Full (strict).
Nếu không dùng Cloudflare proxy, Nginx có thể trực tiếp terminate TLS và chuyển traffic đến ứng dụng WebSocket.
Vì sao reverse proxy WebSocket hay lỗi?
WebSocket khác request HTTP ngắn ở các điểm:
Upgrade và Connection
Bình luận
là hop-by-hop headers, proxy phải chuyển tiếp có chủ đích.
Backend phải trả 101 Switching Protocols.
Connection có thể tồn tại hàng giờ nhưng không phải lúc nào cũng có dữ liệu.
Một timeout ở bất kỳ lớp nào cũng có thể đóng tunnel.
Khi reconnect, Load Balancer có thể chọn server khác.
Nếu TLS mode giữa Cloudflare và origin sai, handshake có thể thất bại trước khi tới ứng dụng.
Cấu hình Nginx nền tảng
Đặt map trong block http, không đặt trong server hoặc location:
proxy_pass có hoặc không có dấu / cuối sẽ ảnh hưởng cách Nginx rewrite URI. Nếu backend mong đợi /ws/, hãy kiểm tra đường dẫn thực thay vì sao chép cấu hình máy móc.
Connection Timeout: đừng chỉ đặt một con số cực lớn
Theo tài liệu Nginx, proxy_read_timeout mặc định là 60 giây và được tính giữa hai lần đọc liên tiếp, không phải tổng thời lượng connection. Nếu upstream không gửi byte nào trong khoảng đó, Nginx có thể đóng tunnel.
Cách xử lý đúng gồm hai phần:
Đặt timeout đủ lớn cho nhịp heartbeat dự kiến.
Gửi heartbeat ở tầng WebSocket/ứng dụng với chu kỳ ngắn hơn timeout nhỏ nhất trên toàn đường truyền.
Ví dụ server dùng thư viện ws:
Với browser API, JavaScript không phát WebSocket Ping control frame trực tiếp. Có thể dùng heartbeat ở tầng ứng dụng:
Server cần trả pong hoặc traffic phù hợp. Tần suất 30 giây chỉ là điểm khởi đầu; phải chọn thấp hơn idle timeout nhỏ nhất và có jitter nếu số client rất lớn.
Cloudflare và WebSocket timeout
Khi DNS record bật proxy, connection đi qua Cloudflare trước Nginx. Cloudflare hỗ trợ WebSocket, nhưng có thể đóng connection khi không có dữ liệu trong một khoảng thời gian; một số giới hạn có thể phụ thuộc gói và cấu hình hiện tại.
Không nên hard-code một “con số Cloudflare” lấy từ blog cũ. Hãy:
Kiểm tra tài liệu và dashboard của gói đang dùng.
Đảm bảo heartbeat nhỏ hơn giới hạn idle thấp nhất của Cloudflare, Nginx, Load Balancer và ứng dụng.
Ghi lại close code, thời điểm disconnect và lớp phát hiện lỗi.
Thêm exponential backoff + jitter khi reconnect.
Sau reconnect, resubscribe room/channel và đồng bộ dữ liệu bị lỡ.
Ví dụ reconnect phía client:
Sticky session: khi nào thực sự cần?
Một WebSocket đã Upgrade là một TCP connection duy nhất. Connection đó tự nhiên tiếp tục tới cùng origin cho đến khi đóng. Sticky session không chuyển frame giữa các server.
Affinity trở nên quan trọng trong các trường hợp:
Socket.IO vẫn cho phép HTTP long-polling; nhiều request của cùng session phải tới cùng node.
Handshake hoặc session state chỉ nằm trong RAM của một node.
Client reconnect và cần quay lại node có state cục bộ.
Một luồng nhiều request trước/sau Upgrade phụ thuộc server-local state.
Nếu hệ thống dùng WebSocket-only và mọi state quan trọng nằm trong database/Redis, thường không cần ép reconnect quay về node cũ.
Sticky session tại Nginx
Nginx Open Source có thể dùng ip_hash:
Nhược điểm: nhiều client sau cùng NAT có thể dồn vào một node; IP có thể thay đổi; khi Cloudflare đứng trước mà real IP chưa được khôi phục đúng, mọi request có thể trông như đến từ Cloudflare edge.
Cookie-based stickiness có kiểm soát tốt hơn, nhưng khả năng cấu hình phụ thuộc sản phẩm Nginx hoặc lớp Load Balancer đang sử dụng.
Session Affinity tại Cloudflare Load Balancing
Nếu dùng Cloudflare Load Balancer với nhiều origin pool, bật Session Affinity để request/reconnect của cùng client ưu tiên cùng origin. Tuy nhiên:
Affinity không thay thế shared state.
Khi origin unhealthy hoặc bị loại khỏi pool, client vẫn phải kết nối lại tới node khác.
Room, authentication context và subscription cần có khả năng khôi phục.
Cross-node broadcast vẫn cần Redis Adapter hoặc messaging backplane tương đương.
SSL/TLS Termination đúng cách
Mô hình 1: Nginx trực tiếp terminate TLS
Phù hợp khi không bật Cloudflare proxy. Nginx dùng chứng chỉ công khai từ CA như Let's Encrypt.
Mô hình 2: Cloudflare và Nginx đều dùng TLS
Đây là lựa chọn nên dùng khi Cloudflare đứng trước origin:
Cloudflare SSL/TLS mode: Full (strict).
Origin dùng certificate hợp lệ với hostname: certificate công khai hoặc Cloudflare Origin CA.
Không dùng Flexible cho production vì hop Cloudflare → origin không được bảo vệ như HTTPS end-to-end.
Bật TLS 1.2/1.3 và quản lý việc gia hạn certificate.
Bảo vệ origin sau Cloudflare
Nếu origin vẫn truy cập công khai, kẻ tấn công có thể bỏ qua Cloudflare bằng cách gọi trực tiếp IP origin. Các biện pháp thường dùng:
Firewall chỉ cho phép dải IP Cloudflare vào cổng 443.
Cập nhật dải IP từ nguồn chính thức, không chép danh sách cũ vĩnh viễn.
Dùng Authenticated Origin Pulls khi phù hợp.
Không lộ IP origin trong DNS record phụ, email header hoặc lịch sử triển khai.
Yêu cầu secret header/mTLS ở lớp nội bộ nếu kiến trúc hỗ trợ.
Khôi phục IP client thật
Sau Cloudflare, $remote_addr mặc định thường là IP Cloudflare. Có thể cấu hình Real IP module:
Chỉ tin CF-Connecting-IP khi request chắc chắn đến từ Cloudflare. Nếu origin mở cho mọi IP, client có thể tự giả header.
Sau khi cấu hình đúng, rate limit và log nên dùng địa chỉ client đã được khôi phục.
Rate limit handshake, không giới hạn frame một cách mù quáng
Nginx HTTP rate limiting chủ yếu áp dụng cho handshake/reconnect, không tự kiểm soát từng WebSocket frame sau Upgrade.
Ứng dụng vẫn phải giới hạn message rate, payload size, room join, authentication attempts và outbound backpressure.
Docker Compose minh họa
depends_on không bảo đảm ứng dụng đã ready. Cần health check, retry hợp lý và readiness tại nền tảng triển khai.
Chẩn đoán theo triệu chứng
Kiểm thử trước production
Kiểm tra Nginx:
Kiểm tra TLS origin:
Kiểm tra WebSocket end-to-end:
Kịch bản bắt buộc:
Giữ connection idle lâu hơn heartbeat interval.
Tắt một backend node và quan sát reconnect.
Reload Nginx khi đang có connection.
Tạm ngắt Redis/backplane nếu hệ thống nhiều node.
Test qua Cloudflare và test trực tiếp origin trong mạng quản trị.
Xác minh certificate rotation.
Test 1.000–10.000 connection, reconnect storm và slow consumer.
Xác minh client resubscribe và resync sau reconnect.
1upstream websocket_backend {2 ip_hash;3 server ws-node-a:3000;4 server ws-node-b:3000;5 server ws-node-c:3000;6}
Codejavascript
1# File này phải chứa các dòng set_real_ip_from từ danh sách IP Cloudflare chính thức.2include /etc/nginx/cloudflare-ips.conf;34real_ip_header CF-Connecting-IP;5real_ip_recursive on;
Hướng dẫn cấu hình Nginx Reverse Proxy và SSL HTTPS bằng Docker Compose, Certbot và Let's Encrypt, kèm bảo mật TLS, tự động gia hạn và troubleshooting production.