Kalzit is in early development. These pages describe what exists today and change as the milestones progress.

HTTPS and reverse proxies

Kalzit can serve HTTPS itself, or it can sit behind a reverse proxy that handles TLS. Both work the same on Windows and Linux.

HTTPS with your own certificate

Kalzit uses rustls, so no OpenSSL is involved. Give it a certificate chain and a private key as PEM files and an HTTPS address:

[server]
http_addr = "0.0.0.0:80"
https_addr = "0.0.0.0:443"
public_url = "https://kalzit.example.com"

[tls]
cert_file = "/etc/kalzit/tls/fullchain.pem"
key_file = "/etc/kalzit/tls/privkey.pem"

What happens then:

  • HTTPS offers TLS 1.2 and 1.3 (tls.min_version = "1.3" allows only 1.3) and HTTP/2.
  • Plain HTTP on http_addr redirects to HTTPS, except /healthz and /readyz. Set server.redirect_http_to_https = false to serve the app on both, or set http_addr = "" to turn plain HTTP off.
  • Every 60 seconds Kalzit checks whether the files changed and loads a renewed certificate without a restart. If a new file cannot be read, the old certificate stays in use and the log says why.
  • HTTPS responses carry Strict-Transport-Security for one year.

Automatic certificates through ACME (Let’s Encrypt) are planned for M5. Until then, renew the files with the tool of your choice; Kalzit picks them up.

HSTS is sticky. Once a browser has seen the header, it refuses plain HTTP for that host for the whole duration. While you try things out, set security.hsts_max_age_secs to a few minutes, or security.hsts = false.

Behind a reverse proxy

Let the proxy terminate TLS and keep Kalzit on plain HTTP on the loopback interface:

[server]
http_addr = "127.0.0.1:3000"
public_url = "https://kalzit.example.com"

[security]
trusted_proxies = ["127.0.0.1/32", "::1/128"]

trusted_proxies matters. Kalzit reads X-Forwarded-For, X-Forwarded-Proto and X-Forwarded-Host only from these addresses; from everyone else the headers are ignored, so a client cannot pretend to come from somewhere else. Without it, Kalzit sees every request as plain HTTP from the proxy: session cookies lose the Secure flag, HSTS is not sent and the sign-in limit counts all users as one address.

The client address is the first address in X-Forwarded-For, read from the right, that is not a trusted proxy. Serve Kalzit at the root of its own host name; a sub-path such as /kalzit is not supported yet.

If the proxy already sends HSTS, set security.hsts = false so the header is not sent twice.

Caddy

Caddy gets a certificate on its own and sets the forwarded headers by default:

kalzit.example.com {
	reverse_proxy 127.0.0.1:3000
}

nginx

server {
    listen 443 ssl;
    http2 on;
    server_name kalzit.example.com;

    ssl_certificate     /etc/ssl/kalzit/fullchain.pem;
    ssl_certificate_key /etc/ssl/kalzit/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;
    }
}

Headers Kalzit always sends

Every response carries a Content Security Policy without inline scripts or styles, X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, a Permissions-Policy that turns off camera, microphone and location, and Cross-Origin-Opener-Policy: same-origin. Requests that change data from a foreign origin are rejected.