Sổ tay kỹ thuật

mTLS với nginx – CA nội bộ, ssl_verify_client, test bằng curl

how-to#tls#nginx#pki#security· cập nhật 2026-11-06

Dùng khi muốn chỉ client có cert do CA nội bộ cấp (collector, agent, script automation) mới gọi được API sau nginx.

Sửa trang này
Mục lục

Tóm tắt: Tạo CA nội bộ dành riêng cho client, ký cert client có extendedKeyUsage=clientAuth. Trong nginx: ssl_client_certificate trỏ tới CA đó, ssl_verify_client on, chuyển danh tính client ($ssl_client_s_dn, $ssl_client_serial) xuống upstream. Test bằng curl --cert client.crt --key client.key. Khái niệm mTLS xem TLS và PKI; TLS phía server và reverse proxy xem Nginx reverse proxy + TLS.

Luồng hoạt động

sequenceDiagram participant C as Client (collector01) participant N as nginx participant U as Upstream API C->>N: ClientHello N->>C: Certificate (server) + CertificateRequest C->>N: Certificate (client.crt) + CertificateVerify N->>N: Verify với client-ca.crt, CRL, thời hạn alt hợp lệ N->>U: Request + X-Client-DN, X-Client-Serial U->>C: 200 else không có cert hoặc cert sai N->>C: 400 Bad Request end

Cấu hình

CA và cert client

Dùng CA riêng cho client, không dùng chung với CA ký cert server: nginx tin mọi cert do CA trong ssl_client_certificate ký.

# CA cho client (lab; production nên dùng Vault PKI hoặc step-ca)
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
  -keyout client-ca.key -out client-ca.crt -days 1825 -subj "/CN=Example Client CA" \
  -addext "basicConstraints=critical,CA:TRUE" -addext "keyUsage=critical,keyCertSign,cRLSign"
 
# Key + CSR trên máy client
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
  -keyout client.key -out client.csr -subj "/CN=collector01/O=netops"
 
# CA ký, tự đặt extension (không chép từ CSR)
cat > client.ext <<'EOF'
basicConstraints=CA:FALSE
keyUsage=critical,digitalSignature
extendedKeyUsage=clientAuth
subjectAltName=DNS:collector01.example.com
EOF
openssl x509 -req -in client.csr -CA client-ca.crt -CAkey client-ca.key -CAcreateserial \
  -out client.crt -days 90 -extfile client.ext
 
openssl verify -CAfile client-ca.crt -purpose sslclient client.crt   # client.crt: OK

Các lệnh openssl khác (đọc cert, PKCS#12): OpenSSL cheatsheet.

nginx

/etc/nginx/conf.d/api-mtls.conf
server {
    listen 443 ssl;
    server_name api.example.com;
 
    ssl_certificate     /etc/nginx/tls/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/tls/api.example.com/privkey.pem;
 
    # mTLS
    ssl_client_certificate /etc/nginx/tls/client-ca.crt;   # CA tin cho cert client
    ssl_verify_client on;                                   # optional: cho phép không có cert
    ssl_verify_depth 2;
    # ssl_crl /etc/nginx/tls/client-ca.crl;                 # bật khi có quy trình thu hồi
 
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header X-Client-Verify $ssl_client_verify;
        proxy_set_header X-Client-DN     $ssl_client_s_dn;
        proxy_set_header X-Client-Serial $ssl_client_serial;
    }
}
Giá trị ssl_verify_client Hành vi
on Bắt buộc cert hợp lệ, không có/không hợp lệ trả 400
optional Xin cert nhưng không bắt buộc; kiểm tra $ssl_client_verify trong config/app
optional_no_ca Xin cert, không verify với CA (để app tự kiểm tra), ít dùng
off Mặc định, không xin cert

Chỉ bắt buộc mTLS cho một số path: đặt ssl_verify_client optional; ở server, rồi trong location:

location /admin/ {
    if ($ssl_client_verify != SUCCESS) { return 403; }
    proxy_pass http://127.0.0.1:8080;
}

Phân quyền theo client cụ thể: map $ssl_client_s_dn $mtls_allowed { ... } hoặc để app đọc header X-Client-DN. Upstream phải chỉ nghe localhost/mạng nội bộ và ghi đè các header này, nếu không client có thể tự giả header.

sudo nginx -t && sudo systemctl reload nginx

Thu hồi cert client

# openssl ca cần file cấu hình tối thiểu: database (index.txt), crlnumber, default_md, default_crl_days
openssl ca -config ca.cnf -keyfile client-ca.key -cert client-ca.crt -revoke client.crt
openssl ca -config ca.cnf -keyfile client-ca.key -cert client-ca.crt -gencrl -out client-ca.crl
openssl verify -crl_check -CAfile client-ca.crt -CRLfile client-ca.crl client.crt   # certificate revoked

Khi bật ssl_crl, CRL hết hạn (nextUpdate qua) thì nginx từ chối mọi client: phải sinh lại CRL định kỳ rồi reload nginx. Cert client ngắn hạn (vài ngày tới 90 ngày) thường đơn giản hơn duy trì CRL.

Lệnh

# Có cert: 200
curl --cert client.crt --key client.key https://api.example.com/health
# Server dùng CA nội bộ
curl --cacert server-ca.crt --cert client.crt --key client.key https://api.example.com/health
# Không cert: 400 "No required SSL certificate was sent"
curl -sS -o /dev/null -w '%{http_code}\n' https://api.example.com/health
# Xem server có gửi CertificateRequest và danh sách CA chấp nhận
openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>/dev/null | sed -n '/Acceptable client certificate CA names/,/Requested/p'
# Python requests
python3 -c 'import requests; print(requests.get("https://api.example.com/health", cert=("client.crt","client.key")).status_code)'

Lỗi thường gặp

Triệu chứng Nguyên nhân Cách xử lý
400 No required SSL certificate was sent Client không gửi cert (thiếu --cert, hoặc CA client không có trong danh sách server gửi) Kiểm tra lệnh client; xem "Acceptable client certificate CA names"
400 The SSL certificate error Cert client không ký bởi ssl_client_certificate, hết hạn, thiếu intermediate, bị CRL error.log của nginx ghi lý do; openssl verify -CAfile client-ca.crt client.crt
Lỗi verify khi CA có intermediate ssl_verify_depth quá nhỏ hoặc client không gửi intermediate Tăng depth; client gửi client.crt gộp intermediate
unsupported certificate purpose Cert client thiếu clientAuth trong EKU Ký lại với extendedKeyUsage=clientAuth
Mọi client bị từ chối đột ngột CRL hết hạn Sinh lại CRL, tự động hoá bằng timer
Cert của CA server (Let's Encrypt) cũng vào được ssl_client_certificate trỏ nhầm bundle CA public Chỉ để CA client nội bộ trong file này
curl báo unable to set private key file Key không khớp cert hoặc sai định dạng So hash public key (xem OpenSSL cheatsheet)

Tham khảo