Topics: HTTPS & Certificates

HTTPS & Certificates

FluxDeploy includes a built-in Kestrel HTTPS server - no external reverse proxy (IIS, nginx, Apache) is required. All communication is encrypted with TLS 1.2+ and certificate pinning prevents man-in-the-middle attacks.

This page covers the certificate FluxDeploy itself serves. For the certificates FluxDeploy issues to the services it deploys - Connected Cache nodes today - see Certificate Authority.

No reverse proxy

Put FluxDeploy directly on the network. It does not read X-Forwarded-For or X-Forwarded-Proto, so behind a TLS-terminating proxy or load balancer every address-based control - login lockout, the IP allowlist, the LAN-only first-run and relay tech pages, and the address written to the audit log - would see the proxy instead of the client, and the session cookie would be issued without the Secure flag because the hop to Kestrel is plain HTTP. If you need a different hostname or certificate, change the Server Address and install the certificate here rather than fronting the server with a proxy.

Certificate Sources

Configure the certificate source in appsettings.json under the Https section, or use the defaults:

Auto (Default)

A self-signed RSA-4096 certificate is generated on first startup:

  • Saved to certs/fluxdeploy-auto.pfx with a random password
  • Subject Alternative Names (SAN) include: localhost, machine hostname, all local IP addresses
  • Valid for 365 days (configurable)
  • Auto-renewed on startup when expiring within 30 days
  • SHA-256 thumbprint logged at startup and visible in Settings > Security > HTTPS Certificate

PFX File

Load a certificate from a .pfx file on disk:

"Https": {
  "CertificateSource": "File",
  "CertificatePath": "C:\\certs\\fluxdeploy.pfx",
  "CertificatePassword": "your-password"
}

Windows Certificate Store

Load by thumbprint from the Windows certificate store:

"Https": {
  "CertificateSource": "Store",
  "CertificateThumbprint": "AB12CD34...",
  "CertificateStoreName": "My",
  "CertificateStoreLocation": "LocalMachine"
}

Certificate Pinning

Agent to Server

The SHA-256 thumbprint of the server's TLS certificate is embedded in the boot image at build time (in agent.config.json). The agent validates the server cert against this thumbprint on every HTTPS request.

Important: When changing certificates, boot images must be rebuilt with the new thumbprint. Existing boot images will fail to connect after a certificate change.

Relay to Core

Core sends its active certificate thumbprint in every heartbeat response. The relay uses a dynamic validation callback that updates in memory - no HttpClient recreation needed. On first connection with no known thumbprint, the relay accepts any certificate (trust-on-first-use).

When Core's certificate changes (renewal or replacement), the new thumbprint is automatically pushed to all relays on their next heartbeat. The thumbprint is persisted to relay.json so it survives relay restarts.

For an ACME-issued certificate this is a handshake, not a push: the new certificate is staged, its thumbprint is published in every heartbeat response, and the cutover happens only once every live relay has echoed it back. Settings → Security → Certificates → How they are issued → Certificate rotation shows where that has got to - what is staged, which relays have acknowledged, and which have not - and holds the controls to request a certificate, force the cutover, or discard a staged one. See Certificate Authority.

Configuration Reference

"Https": {
  "Enabled": true,
  "HttpsPort": 8400,
  "HttpPort": 0,
  "BindAddress": "",
  "CertificateSource": "Auto",
  "AutoCertSubject": "FluxDeploy Server",
  "AutoCertValidityDays": 365
}
Setting Description
HttpsPort HTTPS listen port (default 8400 - 443 and 80 are reserved for Connected Cache)
HttpPort HTTP listen port (0 = disabled, and that is correct even with ACME - see below)
BindAddress IP to bind to (empty = all interfaces)
AutoCertSubject CN for self-signed cert
AutoCertValidityDays Self-signed cert validity (30-3650 days)

ACME and port 80: FluxDeploy borrows it

RFC 8555 fixes the http-01 challenge on port 80. The certificate authority connects to http://<your-server>/.well-known/acme-challenge/... and will not use another port; there is no setting on either side to change it.

FluxDeploy does not hold that port. It borrows port 80 only for the moments an order is in flight, and gives it straight back. HttpPort ships as 0 and should stay there: nothing answers port 80 on a healthy server between renewals, and that is the expected state, not a fault.

What that looks like per host:

Host What happens during an order
Nothing owns port 80 FluxDeploy binds it transiently, answers the challenge, releases it
A Connected Cache node is running The node's own port-80 forwarding rule is captured exactly as configured, removed for the few seconds of validation, then restored and verified back
A reverse proxy owns port 80 Point it at this server. The borrow is a no-op and the proxy keeps serving

A cache node CAN run on the same server. This used to be impossible: FluxDeploy held port 80 permanently, and Connected Cache's own pre-flight refuses to deploy unless port 80 is free, so the two could not coexist. Borrowing removes that conflict. The node keeps the port except during validation, and a window is never opened while a cache-node deploy is in progress.

If the service is stopped or the machine restarts mid-order, the captured rule is restored on the next start - it is recorded to disk before anything is removed, so a cache node cannot be left quietly not serving.

Legacy installs. ACMEHTTPPORT=80 at install time, and a manual HttpPort: 80 in appsettings.json, both still work: the borrow sees the port already bound and does nothing. Neither is needed any more, and on a host running a cache node a permanent binding is actively harmful, so prefer leaving HttpPort at 0.

Settings → Security → Certificates → How they are issued → Test connection checks this for you. It registers the account and then publishes a token and reads it back through port 80, so it distinguishes "nothing is listening" from "something else answers on port 80" (IIS is the usual culprit on Windows) - the fixes are different.

Only port 80 needs to be reachable, and only during issuance and renewal. HTTPS traffic keeps using HttpsPort, and nothing about your certificate is served over port 80.

Viewing Certificate Info

The current certificate details (subject, issuer, expiry, SHA-256 thumbprint, bound ports) are available at:

  • Settings > Security > HTTPS Certificate in the UI
  • GET /api/settings/certificate API endpoint