Reverse Proxy
Configure HTTPS and WebSocket for Kuboard (via an Nginx reverse proxy or a Kubernetes Ingress) for use in production.
Why Use a Reverse Proxy
Kuboard uses plain HTTP by default so that you can try it out quickly. In production, make sure HTTPS is enabled. You can either enable HTTPS directly on a Kubernetes Ingress and forward traffic to the Kuboard Service, or set up your own Nginx in front of Kuboard and configure HTTPS on it.
Things to Note
- WebContextRoot (root path): Kuboard must be served from the root path, e.g.
https://kuboard.yourcompany.com/; sub-paths such ashttps://yourcompany.com/kuboard/are not supported. - WebSocket must be enabled: the terminal and log features rely on WebSocket, so the reverse proxy must allow WebSocket upgrade requests, otherwise the terminal and logs will not work.
- Forward
X-Forwarded-Host: when OIDC single sign-on is enabled, the callback URL is built from the origin registered in the Allowed Frontend Origins whitelist, so that whitelist must contain the address (including the port) users actually visit in the browser. SettingX-Forwarded-Hostfrom$http_host(which keeps the port) is still recommended;Host $hostdrops the port and affects other features that need to reconstruct the external address. The directive is harmless when OIDC is off, so it is best added unconditionally. - Page caching: Kuboard automatically sends a no-cache policy for the page entry (
index.html) and a long-lived cache policy for hashed static assets (/assets/), so browsers load the new frontend immediately after an upgrade. Do not add a blanketexpiresorCache-Controlforlocation /on your reverse proxy, and avoidproxy_cache/CDN caching of the entry page — otherwise browsers may keep serving the old page after an upgrade (symptoms include unresponsive UI and broken layout). If you want to cache for speed, configure long-lived caching for the/assets/path only. - Large file uploads: the file browser's HTTP streaming upload uses
/k8s-upload/, and the request body is subject to the reverse proxy's size limit (Nginx defaults toclient_max_body_size 1m), which makes any larger file fail with 413 (shown as "Upload failed: HTTP 413"). Raise the limit in thelocation /k8s-upload/block below, and keepproxy_request_buffering offso bytes are forwarded as they arrive instead of being written to a full temporary file first. - Large file downloads: the file browser's HTTP streaming download uses
/k8s-download/, and the response is a chunked stream. A reverse proxy buffers response bodies by default, which makes large downloads stall or pulls the whole file into the proxy's memory. Disableproxy_bufferingin thelocation /k8s-download/block below, and raiseproxy_read_timeout. - Resource lists with many namespaces selected: the request line grows with the number of selected namespaces. Kuboard itself allows 32KB (
KUBOARD_MAX_HTTP_REQUEST_HEADER_SIZE), but nginx allows only about 8k by default (client_header_buffer_size 1k+large_client_header_buffers 4 8k); beyond that it returns 400 / 414 and the list page simply fails to open. Raise both directives in theserverblock below to at least the Kuboard value.
Nginx Configuration Example
When using Nginx as the reverse proxy, replace the two addresses marked in the comments with your domain and Kuboard node address, then use it directly; if HTTPS is enabled on Nginx, also uncomment the # proxy_set_header X-Forwarded-Proto https; line in each location block.
server {
listen 80;
server_name kuboard.yourdomain.com; # replace with your domain
# Request line + header limit: Kuboard allows 32KB by default, nginx only about 8k.
# Without raising these, selecting many namespaces on a resource list returns
# 400 / 414 (the list page simply fails to open).
client_header_buffer_size 16k;
large_client_header_buffers 4 32k;
location / {
proxy_pass http://192.168.2.39:32567; # replace with your node address
proxy_http_version 1.1;
proxy_pass_header Authorization;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# $http_host keeps the port, needed to build the OIDC callback URL; $host alone drops it
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Port $server_port;
# proxy_set_header X-Forwarded-Proto https; # enable this if you have enabled HTTPS on the reverse proxy
}
location /k8s-ws/ {
proxy_pass http://192.168.2.39:32567; # replace with your node address
proxy_http_version 1.1;
proxy_pass_header Authorization;
proxy_set_header Upgrade "websocket";
proxy_set_header Connection "upgrade";
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-Host $http_host;
proxy_set_header X-Forwarded-Port $server_port;
# proxy_set_header X-Forwarded-Proto https; # enable this if you have enabled HTTPS on the reverse proxy
}
# File browser HTTP streaming download (chunked response, buffering must be off)
location /k8s-download/ {
proxy_pass http://192.168.2.39:32567; # replace with your node address
proxy_http_version 1.1;
proxy_pass_header Authorization;
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-Host $http_host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_buffering off; # buffering off, otherwise large files stall / balloon memory
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
# proxy_set_header X-Forwarded-Proto https; # enable this if you have enabled HTTPS on the reverse proxy
}
# File browser HTTP streaming upload (large files need the body size limit raised)
location /k8s-upload/ {
proxy_pass http://192.168.2.39:32567; # replace with your node address
proxy_http_version 1.1;
proxy_pass_header Authorization;
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-Host $http_host;
proxy_set_header X-Forwarded-Port $server_port;
# proxy_set_header X-Forwarded-Proto https; # enable this if you have enabled HTTPS on the reverse proxy
client_max_body_size 0; # 0 = unlimited; the 1m default returns 413 for larger files
proxy_request_buffering off; # forward as bytes arrive instead of buffering the whole file
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
# Swagger / OpenAPI docs (needed when KUBOARD_SWAGGER_ENABLED=true)
# Note: the location must NOT end with a slash. With /v3/api-docs/ nginx
# redirects /v3/api-docs to /v3/api-docs/ with a 301, breaking both the
# un-suffixed path and the per-group paths.
location /v3/api-docs {
proxy_pass http://192.168.2.39:32567; # replace with your node address
proxy_http_version 1.1;
proxy_pass_header Authorization;
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-Host $http_host;
proxy_set_header X-Forwarded-Port $server_port;
# proxy_set_header X-Forwarded-Proto https; # enable this if you have enabled HTTPS on the reverse proxy
}
gzip on;
}OpenAPI document URLs
Once /v3/api-docs is proxied, both /v3/api-docs (the complete description) and /v3/api-docs/{group-name} (a single group, e.g. /v3/api-docs/cluster.kuboard.cn) are reachable from the browser; see API Reference for the list of group names. Swagger UI additionally needs the /swagger/ path — add another location /swagger/ block in the same way if you need it.
Ingress Configuration Example
When using nginx-ingress as the Ingress Controller, save the following configuration as ingress.yaml, replace the domain and the certificate Secret (which must be created in advance), then run kubectl apply -f ingress.yaml; for other Ingress controllers (traefik, ingress-nginx, etc.), refer to the official documentation and configure WebSocket and PassHeader yourself.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: kuboard
namespace: kube-system
annotations:
nginx.org/websocket-services: kuboard
nginx.org/proxy-pass-headers: 'Upgrade,Connection,Authorization'
spec:
rules:
- host: demo.kuboard.cn
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: kuboard
port:
name: http
tls:
- hosts:
- demo.kuboard.cn
secretName: demo.kuboard.cn- The two annotations
nginx.org/websocket-servicesandnginx.org/proxy-pass-headersenable WebSocket and forward the authorization header (for session affinity or display name, you can add thenginx.com/sticky-cookie-servicesandk8s.kuboard.cn/displayNameannotations yourself).
Large uploads need the Ingress request body limit raised
The file browser's HTTP streaming upload (/k8s-upload/) is also subject to the Ingress controller's request body size limit, which is usually 1m by default and returns 413 for anything larger:
- ingress-nginx (
nginx.ingress.kubernetes.io/*):nginx.ingress.kubernetes.io/proxy-body-size: "0", plusnginx.ingress.kubernetes.io/proxy-request-buffering: "off"is recommended; - NGINX Ingress Controller (
nginx.org/*):nginx.org/client-max-body-size: "0".
Check the official documentation of the controller you use for the exact annotation names.
HA Multi-replica: Session Affinity and WebSocket Path Coverage
The examples above target a single-node deployment. If you are using the Kuboard HA deployment (multi-replica + load balancer), the reverse proxy needs three additional adjustments; otherwise terminal, log, and MCP SSE long connections will disconnect frequently:
Add
ip_hashtoupstream: the default round-robin randomly switches WebSocket / SSE long connections between the two replicas, which causes the log stream, terminal, and MCP SSE push to disconnect repeatedly.ip_hashpins the same client to a fixed upstream; combined with kuboard-server's P2-5a log reconnect, it keeps most WebSocket / log streams alive across a rolling restart of a replica:nginxupstream kuboard_backend { ip_hash; server kuboard-1:80; server kuboard-2:80; }On a Kubernetes Ingress,
nginx.com/sticky-cookie-services, or nginx-ingress's built-in session affinity (nginx.org/affinity: cookie+nginx.org/affinity-cookie-name: SERVERID), achieves the same effect.Cover WebSocket upgrades on every path: Kuboard's terminal uses
/k8s-ws/, K8s watch uses/k8s-api/and/api/k8s.kuboard.cn/, and MCP SSE uses/mcpand/mcp-subscriptions/. The original official HA orchestration only configured the WebSocket upgrade on/api/k8s.kuboard.cn/v4/clusters/, which breaks terminal and log features in HA. Follow the complete Nginx example in the HA deployment document to cover all paths.Reasonable timeouts: WebSocket long connections can last for hours (K8s watch keeps pushing, the log stream keeps flowing); the default 60s
proxy_read_timeoutandproxy_send_timeoutwill cut the stream off. Set both globally to 86400s (24h):nginxproxy_read_timeout 86400; proxy_send_timeout 86400;
About P2-5a log reconnect
When ip_hash is unavailable, or an upstream replica actually goes down (for example, after an nginx reload or when the ip_hash key is unstable), the kuboard-server K8sTerminalLogs view automatically reconnects the WebSocket with exponential backoff (1s → 30s, capped at 8 attempts) and restores the log lines produced during the outage from a Redis stream buffer (5-minute TTL). This capability is active in HA mode (KUBOARD_CACHE_PROVIDER=redis); in single-node caffeine mode, where there is no cross-instance need, the buffer degrades to no-op and reconnect only sees the log lines produced from the reconnection point onward.
Related Documentation
- Quick Start — single-node deployment
- High Availability (HA) — multi-replica deployment and load balancing (includes a complete Nginx example)
- Index — other deployment scenarios
- Upgrade — version upgrades and data backup
- Migrating from v3 — migrating from v3 to v4
