Lần đầu setup Kibana phía sau Nginx có thể chạy ngon ngay. Tới khi user lên dashboard, click Edit, nhận error Forbidden: missing XSRF token. Hoặc Discover bị treo vì websocket không upgrade. Hoặc auto-complete suggestion mất 10 giây vì Cloudflare strip một header lạ. Tất cả đều là Kibana đối nghịch với reverse proxy mặc định.
Đây là bài 18 trong series Kibana từ A đến Z. Sau bài này bạn sẽ làm được:
- Cấu hình Nginx reverse proxy cho Kibana đúng chuẩn
- Hiểu
server.publicBaseUrlvà khi nào set - Xử lý header
kbn-xsrf, websocket, body size, header size - Đặt Cloudflare đúng cho Kibana mà không break login plus Discover
- Tránh pitfall MitM browser cache do path khác
Phần 1: Vì sao đặt Kibana sau reverse proxy
Lý do phổ biến:
- TLS termination tại proxy, Kibana nói plain HTTP nội bộ.
- Cho phép custom domain (
kibana.example.com) thay vìexample.com:5601. - WAF, rate limit, geofencing.
- Multi-instance Kibana behind load balancer.
- Tag Kibana sau VPN hoặc Cloudflare Access cho zero-trust.
Pattern chuẩn:
Browser -- HTTPS --> Cloudflare / WAF -- HTTPS --> Nginx -- HTTP/HTTPS --> Kibana :5601 -- HTTPS --> Elasticsearch :9200
Bài này lo phần Browser -> Cloudflare -> Nginx -> Kibana. Bài 19 lo phần Kibana -> ES plus Beats -> ES.
Phần 2: Nginx config tối thiểu
/etc/nginx/sites-available/kibana.conf:
upstream kibana_backend {
server 127.0.0.1:5601;
keepalive 32;
}
server {
listen 443 ssl http2;
server_name kibana.example.com;
ssl_certificate /etc/letsencrypt/live/kibana.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/kibana.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# Header size: Kibana hay gửi cookie session lớn
large_client_header_buffers 8 64k;
client_max_body_size 100m;
# Timeout phải lớn cho Discover query nặng
proxy_connect_timeout 60s;
proxy_send_timeout 600s;
proxy_read_timeout 600s;
location / {
proxy_pass http://kibana_backend;
# Header chuẩn
proxy_http_version 1.1;
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;
# WebSocket cho Console, ES|QL editor, dev tools
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# KHÔNG strip kbn-xsrf
proxy_pass_request_headers on;
}
}
server {
listen 80;
server_name kibana.example.com;
return 301 https://$server_name$request_uri;
}
Bốn điểm load-bearing:
large_client_header_buffers 8 64k: cookie Kibana session lớn (nhất là khi bậtxpack.security), default 8k nhỏ nên 400 Bad Request.proxy_read_timeout 600s: Discover query có thể chạy phút. Default 60s là thấp.UpgradeplusConnection: upgrade: cần cho websocket. Dev Tools console không bật websocket sẽ không gửi request được.proxy_pass_request_headers on: default ON, nhưng nếu có middleware Lua/njs strip header thì XSRF gãy.
Phần 3: server.publicBaseUrl
Trong kibana.yml:
server.publicBaseUrl: "https://kibana.example.com"
Vì sao quan trọng:
- Email alert chứa link tới dashboard. Không set thì link ra
http://localhost:5601/..., user không mở được. - Slack/Discord webhook payload từ alert chứa URL Kibana.
- Cookie SameSite plus Secure attribute tuỳ thuộc URL.
- Reporting (PDF, CSV) build URL absolute.
Lỗi điển hình nếu thiếu: alert email trỏ về localhost, hoặc Kibana log warn server.publicBaseUrl is missing mỗi vài giây.
Khi Kibana mount sub-path (ví dụ https://example.com/kibana/):
server.basePath: "/kibana"
server.rewriteBasePath: true
server.publicBaseUrl: "https://example.com/kibana"
Lúc đó Nginx phải pass đường dẫn nguyên: proxy_pass http://kibana_backend/; (trailing slash quan trọng, không có thì path bị strip).
Phần 4: XSRF header
Mọi POST, PUT, DELETE vào Kibana API yêu cầu một trong các header:
kbn-xsrf: truex-elastic-internal-origin: kibana(cho internal API)kbn-version: <version>(chính xác version đang chạy)
Browser request từ chính Kibana UI tự inject header. Vấn đề: khi reverse proxy plus WAF strip header lạ, browser gửi xong nhưng Kibana không thấy, trả 400 Request must contain the kbn-xsrf header.
Kiểm tra Cloudflare: vào Rules plus Transform Rules plus xem có rule strip header không. Tránh dùng “Add or Modify HTTP Request Header” mà set name là kbn-xsrf rồi clear value, sẽ ghi đè.
Trong Nginx, nếu dùng proxy_set_header với cùng tên, default Nginx XÓA header gốc rồi set giá trị mới. Đừng làm thế với kbn-xsrf. Để default pass through.
API request từ script (curl, Postman, CI) phải tự thêm kbn-xsrf: true:
curl -H "kbn-xsrf: true" \
-H "Authorization: ApiKey $API_KEY" \
-X POST "$KB_URL/api/saved_objects/dashboard"
Phần 5: Cloudflare cho Kibana
Cloudflare hữu ích vì proxy DDoS, WAF, Access (zero-trust). Nhưng có nhiều bẫy:
Bẫy 1: Rocket Loader
Cloudflare plus Speed plus Rocket Loader đặt JS async. Kibana SPA crash vì react app load sai thứ tự. Tắt Rocket Loader cho hostname kibana.example.com.
Bẫy 2: Auto Minify
Minify HTML/CSS/JS. Kibana đã tự minify, double minify dễ hỏng map files. Tắt Auto Minify.
Bẫy 3: Browser Cache TTL
Cloudflare default cache static asset 4 giờ. Sau khi upgrade Kibana version, browser load JS cũ 4h tiếp. Set Browser Cache TTL: Respect Existing Headers hoặc Page Rule riêng cho /bundles/* cache 1 ngày, còn /api/* Bypass Cache.
Bẫy 4: WebSocket support
Cloudflare hỗ trợ WS nhưng phải bật. Network plus WebSockets: ON. Pro plan trở lên mới chạy được lâu (Free plan timeout ~100s).
Bẫy 5: SSL mode Strict vs Full
Flexible SSL (Cloudflare HTTPS, origin HTTP) làm Kibana nhầm scheme và sinh redirect loop. Dùng Full (Strict) với cert hợp lệ ở origin Nginx.
Bẫy 6: Cloudflare Access cookies
Cloudflare Access set cookie CF_Authorization. Cookie này không xung đột Kibana cookie, nhưng tổng cookie có thể vượt header buffer. Tăng large_client_header_buffers lên 16 64k nếu thấy 400 ngẫu nhiên.
Page Rule mẫu cho kibana.example.com/*
| Path | Rule |
|---|---|
/api/* | Cache Level: Bypass |
/internal/* | Cache Level: Bypass |
/bundles/* | Edge Cache TTL 1 day |
/translations/* | Edge Cache TTL 1 day |
* | Disable Rocket Loader |
Phần 6: Cloudflare Access plus Kibana auth
Pattern phổ biến: Cloudflare Access trước, Kibana basic auth sau. User login Google qua Cloudflare, vào Kibana login tiếp.
Trade-off: 2 lần login phiền. Tốt hơn: dùng OIDC realm trong ES, nhận identity từ Cloudflare Access JWT header.
# elasticsearch.yml
xpack.security.authc.realms:
jwt:
cloudflare_jwt:
order: 0
token_type: id_token
allowed_audiences: ["<cloudflare-app-aud>"]
allowed_issuer: "https://<team>.cloudflareaccess.com"
claims.principal: "email"
claims.groups: "groups"
client_authentication.type: "none"
pkc_jwkset_path: "https://<team>.cloudflareaccess.com/cdn-cgi/access/certs"
Kibana hỗ trợ pass JWT từ header Authorization: Bearer <token> qua reverse proxy. Cloudflare Access set header CF-Access-Jwt-Assertion, có thể rewrite trong Nginx:
proxy_set_header Authorization "Bearer $http_cf_access_jwt_assertion";
Test kỹ trước production. Bug trong field này thường im lặng, user login được nhưng quyền không khớp.
Phần 7: Reporting plus Headless Chrome
Kibana Reporting tạo PDF/PNG bằng Headless Chrome chạy bên trong Kibana process. Chrome đó connect ngược lại Kibana qua server.publicBaseUrl HOẶC xpack.reporting.kibanaServer.hostname nếu khác.
Vấn đề: nếu Kibana ở trong cluster Kubernetes plus public URL là Cloudflare, Headless Chrome trong pod phải đi ra internet plus quay lại pod. Latency cao, đôi khi proxy ngăn.
Fix: set internal hostname cho reporting:
xpack.reporting.kibanaServer.hostname: "kibana.cluster.local"
xpack.reporting.kibanaServer.port: 5601
xpack.reporting.kibanaServer.protocol: "http"
Headless Chrome dùng internal URL, không qua Cloudflare. User vẫn nhận PDF bình thường.
Phần 8: Multi-instance behind load balancer
Khi scale Kibana plus 1 instance, một số state cần shared:
- Saved object encryption key: phải giống nhau trên mọi instance. Set
xpack.encryptedSavedObjects.encryptionKeycùng giá trị 32 char. - Session storage: Kibana lưu session trong index
.kibana_security_session_*. Mọi instance cùng connect tới cùng ES là OK. - Reporting: instance khác nhau có thể nhận report job khác nhau, không conflict.
- Sticky session: KHÔNG cần. Stateless với encryption key giống nhau.
Load balancer health check:
upstream kibana_backend {
server 10.0.1.1:5601 max_fails=3 fail_timeout=30s;
server 10.0.1.2:5601 max_fails=3 fail_timeout=30s;
keepalive 32;
}
Health check endpoint:
GET /api/status
# Response: { "status": { "overall": { "level": "available" } } }
Verify với curl:
curl -s "http://10.0.1.1:5601/api/status" | jq '.status.overall.level'
# "available"
LB drop instance về degraded plus critical. Smart LB như HAProxy, AWS ALB có thể parse JSON cho health check.
Phần 9: Pitfall hay gặp
Pitfall 1: redirect loop khi Cloudflare Flexible SSL
Cloudflare gửi HTTP tới Nginx (Flexible), Nginx redirect 301 sang HTTPS, Cloudflare lại gửi HTTP. Loop. Fix: SSL mode Full (Strict).
Pitfall 2: trailing slash sai
proxy_pass http://kibana_backend; (không slash) plus location / ghép thành /. OK.
proxy_pass http://kibana_backend/; (có slash) plus location /kibana/ strip prefix. Cần khi mount sub-path.
Sai pattern này = URL Kibana đi qua proxy thành https://example.com/kibana//app/discover (double slash) hoặc bị strip mất /kibana.
Pitfall 3: Save dashboard 413 Request Entity Too Large
Dashboard nhiều panel có saved object NDJSON lớn. Mặc định client_max_body_size 1m của Nginx chặn. Tăng lên 100m đề phòng.
Pitfall 4: Discover query timeout
Default proxy_read_timeout 60s cắt giữa chừng query 90s. User thấy 504. Tăng 600s. Đồng thời, tăng xpack.search.sessions.defaultExpiration của Kibana cho saved search session lâu.
Pitfall 5: bookmark sai sau khi đổi domain
User bookmark kibana-old.example.com, đổi sang kibana.example.com. Cookie domain không khớp, login bị reset mỗi tab mới. Fix: 301 redirect ở Nginx của domain cũ:
server {
listen 443 ssl http2;
server_name kibana-old.example.com;
return 301 https://kibana.example.com$request_uri;
}
Pitfall 6: WAF block path API kbn-xsrf
Một số WAF default rule chặn header tên kbn-xsrf vì giống “custom header lạ”. Whitelist header này trong WAF rule.
Cheatsheet
| Setting | Mục đích |
|---|---|
large_client_header_buffers 8 64k | Cookie lớn, header dài |
client_max_body_size 100m | Import NDJSON, save dashboard lớn |
proxy_read_timeout 600s | Discover query lâu |
Upgrade plus Connection: upgrade | Websocket Console, Dev Tools |
server.publicBaseUrl | Email alert link đúng |
server.basePath plus rewriteBasePath | Sub-path mount |
| Cloudflare Rocket Loader | Tắt |
| Cloudflare Auto Minify | Tắt |
| Cloudflare SSL | Full (Strict) |
| Cloudflare WebSockets | ON |
xpack.encryptedSavedObjects.encryptionKey | Multi-instance |
| Health check | GET /api/status |
Lời kết
Reverse proxy là cầu nối giữa Kibana và internet. Sai một header nhỏ thì user thấy “Kibana hỏng”, thực chất chỉ là missing config. Nhớ ba từ: header, timeout, base path. Phần lớn lỗi đứt mạng nằm ở một trong ba mục đó.
Bài 19 đi vào tầng tiếp theo: TLS/SSL end-to-end, cert giữa Kibana, Elasticsearch và Beats. Setup TLS trong cluster là yêu cầu cứng từ ES 8.x, kể cả single-node. Bài đó kèm cách dùng elasticsearch-certutil, distribute cert qua Ansible/Helm và rotate cert mà không downtime.