0

Series Docker & Nginx cho Laravel #3: Reverse Proxy WebSockets & SSL Termination (Trạm cuối)

1. Nỗi đau khi Nginx "cắt cầu" WebSockets

Giao thức WebSockets (WS/WSS) duy trì một đường ống kết nối mở liên tục (Persistent Connection) giữa Client và Server (thông qua Laravel Reverb, Soketi hoặc Node.js Socket.io).

Tuy nhiên, khi Client gửi Request khởi tạo WebSocket đi qua Nginx, Nginx mặc định sẽ coi đó là một Request HTTP bình thường. Sau khoảng 60 giây không thấy dữ liệu gửi thêm, tính năng proxy_read_timeout của Nginx sẽ tự động "đá văng" (ngắt kết nối) Client để giải phóng bộ nhớ. Kết quả: Màn hình giám sát Real-time của bạn liên tục bị disconnect.

2. Dạy Nginx hiểu giao thức "Upgrade"

Để Nginx trở thành một Reverse Proxy chuẩn mực cho WebSockets, chúng ta phải nói cho nó biết cách bắt lấy Header Upgrade từ Client và chuyển nguyên vẹn Header này xuống cho Server xử lý Socket phía sau.

Giả sử trong hệ thống Docker của bạn có thêm một Container chạy máy chủ WebSocket (ví dụ Laravel Reverb chạy ở cổng 8080). Hãy thêm một block location mới vào file nginx/conf.d/default.conf:

server {
    listen 80;
    server_name afc.local;

    # ... (Các cấu hình PHP-FPM ở Bài 1 và 2 giữ nguyên)

    # ĐỊNH TUYẾN DÀNH RIÊNG CHO WEBSOCKETS (Ví dụ đường dẫn /ws)
    location /ws {
        # Trỏ lệnh proxy tới Container WebSocket nội bộ
        proxy_pass http://websocket_server:8080;

        # 3 dòng BẮT BUỘC để kích hoạt WebSocket Upgrade
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";

        # Forward đúng IP thật của Client (Máy trạm FAM/TVM)
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # 🔥 TẮT chế độ tự động ngắt kết nối của Nginx (Giữ kết nối mở trong 7 ngày)
        proxy_read_timeout 604800;
        proxy_send_timeout 604800;
    }
}

Nhờ cấu hình này, khi luồng dữ liệu lỗi phần cứng từ các cổng soát vé truyền về, nó sẽ đi thẳng qua Nginx như một luồng nước xuyên qua ống, tới thẳng màn hình vận hành với độ trễ chưa tới 10ms.

3. SSL Termination: Bọc thép đường truyền

Nếu hệ thống nội bộ truyền tải dữ liệu thẻ từ qua giao thức HTTP (không mã hóa), bất kỳ ai cắm cáp LAN vào mạng đều có thể dùng Wireshark để "ngửi" (sniff) và đọc được toàn bộ mã thẻ của khách hàng. Đây là lỗi bảo mật chí mạng.

Chuẩn Senior: Không để PHP xử lý SSL (chậm và nặng). Hãy cấu hình SSL tại đúng lớp rào chắn ngoài cùng là Nginx (kỹ thuật này gọi là SSL Termination). Nginx sẽ chịu trách nhiệm giải mã HTTPS thành HTTP thuần, sau đó mới đẩy vào cho PHP xử lý.

Đầu tiên, bạn cần có file Chứng chỉ số (.crt) và Chìa khóa riêng (.key). (Trong môi trường Dev, bạn có thể tự tạo file self-signed). Map 2 file này vào container Nginx thông qua docker-compose.yml:

volumes:
      # ...
      - ./ssl/certs:/etc/nginx/ssl:ro

Sau đó, cấu hình lại Nginx để ép buộc chạy HTTPS:

# Block 1: Nhận HTTP và tự động bẻ lái (Redirect) sang HTTPS
server {
    listen 80;
    server_name afc.local;
    return 301 https://$host$request_uri;
}

# Block 2: Xử lý giao dịch mã hóa HTTPS
server {
    listen 443 ssl http2; # Bật giao thức HTTP/2 siêu tốc
    server_name afc.local;

    # Trỏ đường dẫn tới file chứng chỉ
    ssl_certificate /etc/nginx/ssl/afc_local.crt;
    ssl_certificate_key /etc/nginx/ssl/afc_local.key;

    # Cấu hình các thuật toán mã hóa mạnh (Loại bỏ các giao thức cũ SSLv3, TLS 1.0)
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

    # ... (Gắn lại toàn bộ cấu hình PHP-FPM và WebSockets xuống đây)
}

4. Vòng khép kín của Kiến trúc Microservices Cơ bản

Bạn đã làm được điều gì sau chuỗi bài về Docker này?

Thay vì một cái "nồi lẩu thập cẩm" nhét chung vào một Server XAMPP/LAMP ngày xưa, kiến trúc hiện tại của bạn đã là một cỗ máy công nghiệp đích thực:

  1. Nginx (Port 443): Đứng mũi chịu sào, giải mã SSL, chặn DDoS, nén dữ liệu, duy trì WebSockets.
  2. PHP-FPM (Port 9000): Ẩn mình sau lớp vỏ, chỉ chực chờ tính toán logic vé.
  3. Redis (Port 6379): Chịu tải hàng vạn Queue Job và Cache siêu tốc.
  4. Filebeat -> Elasticsearch -> Kibana: Âm thầm giám sát toàn bộ sinh mệnh của hệ thống qua log.

Tất cả được cách ly hoàn hảo bằng Docker Bridge Networks. Ngay cả khi một dịch vụ bị hack hoặc bị sập, các phần khác vẫn được bảo vệ.

Chuỗi kiến trúc hạ tầng Backend bằng Docker cho Laravel đã hoàn thiện. Chúng ta đã đi từ Node.js/TypeScript sang PHP/Laravel, từ mức Code lên đến mức Database, Memory, và Network/DevOps.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.