This document covers TLS and reverse-proxy deployment for KUI. For systemd setup, see deploy/systemd/README.md. For first-run setup and config, see admin-guide.md.
KUI can run with a runtime prefix (--prefix or KUI_PREFIX). All local paths KUI opens are resolved under that directory using a chroot-style rule (see Contained / non-root mode in the Admin Guide). This supports a relocatable tree (for example /opt/kui-run/etc/kui, /opt/kui-run/var/lib/kui) owned by a dedicated user without scattering state across the real /etc and /var.
Permissions: The service user needs read/write access to the prefix tree (config, SQLite and WAL sidecars, git template directory, any TLS PEMs and optional KUI_WEB_DIR). Use chown / chmod (or ACLs) so only that user can read private keys and the database.
systemd: You can keep the unit’s primary ExecStart as an FHS install, or use --prefix with a directory that mirrors the logical layout (etc/kui, var/lib/kui, pool-related paths if defaults apply). A commented ExecStart pattern appears in deploy/systemd/kui.service and deploy/systemd/README.md. WorkingDirectory= is independent of prefix: it does not substitute for --prefix, and relative paths in YAML are still resolved under the prefix when a prefix is set—not relative to WorkingDirectory.
Production: Prefer empty prefix with real host paths when KUI must align with system libvirt pool locations on disk; use an explicit prefix when you intentionally isolate all KUI-managed files under one tree. See the Admin Guide for precedence and libvirt caveats.
| Mode | Use case |
|---|---|
| HTTP-only | Development, local testing |
| Direct TLS | KUI terminates TLS (optional) |
| Reverse proxy | Production: nginx/Caddy terminate TLS, proxy to KUI over HTTP |
For local development, run KUI without TLS:
kui --config /etc/kui/config.yaml --listen :8080Default listen address is :8080. Override with --listen or KUI_LISTEN.
KUI can terminate TLS when both --tls-cert and --tls-key are provided:
kui --config /etc/kui/config.yaml \
--listen :8443 \
--tls-cert /etc/kui/tls/cert.pem \
--tls-key /etc/kui/tls/key.pem- Both flags are required together; using only one returns an error.
- TLS is configured via flags only; environment variables are not supported.
- Use PEM-encoded certificate and private key files.
[Service]
ExecStart=/usr/local/bin/kui --config /etc/kui/config.yaml \
--listen 127.0.0.1:8443 \
--tls-cert /etc/kui/tls/cert.pem \
--tls-key /etc/kui/tls/key.pemFor production, terminate TLS at a reverse proxy (nginx or Caddy) and proxy to KUI over HTTP. This allows:
- Centralized certificate management (e.g. Let's Encrypt)
- Rate limiting, logging, and hardening at the proxy
- KUI listening on localhost only
The proxy must preserve:
-
WebSocket upgrades for console streaming:
GET /api/hosts/{host_id}/vms/{uuid}/vnc— noVNC WebSocketGET /api/hosts/{host_id}/vms/{uuid}/serial— xterm.js serial WebSocket
-
Server-Sent Events (SSE) for real-time updates:
GET /api/events— long-lived stream (Connection: keep-alive,Cache-Control: no-cache,Content-Type: text/event-stream)
WebSocket upgrade headers (Upgrade, Connection, Sec-WebSocket-Key, Sec-WebSocket-Version, etc.) must be forwarded to KUI.
Place the map block in the http {} context (e.g. in nginx.conf or an included file):
# In http {} block:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl;
server_name kui.example.com;
ssl_certificate /etc/ssl/certs/kui.example.com.crt;
ssl_certificate_key /etc/ssl/private/kui.example.com.key;
location / {
proxy_pass http://127.0.0.1:8080;
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 support (VNC, serial console)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 86400;
}
location /api/events {
proxy_pass http://127.0.0.1:8080;
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;
# SSE: keep connection open, disable buffering
proxy_set_header Connection "";
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 86400;
chunked_transfer_encoding off;
}
}KUI should listen on 127.0.0.1:8080 when behind this proxy:
kui --config /etc/kui/config.yaml --listen 127.0.0.1:8080kui.example.com {
tls /etc/caddy/certs/kui.example.com.crt /etc/caddy/certs/kui.example.com.key
reverse_proxy http://127.0.0.1:8080 {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
# WebSocket and SSE are handled automatically by Caddy's reverse_proxy
# when Upgrade header is present; no extra config needed.
}
}Caddy automatically handles WebSocket upgrades and long-lived SSE connections. KUI should listen on 127.0.0.1:8080.
Proxy requirements: Forward WebSocket upgrade headers (Upgrade, Connection, etc.) and avoid buffering SSE (GET /api/events). See examples above.