Via

Certificates

Via public traffic can use public ACME, an internal CA, or uploaded certificates. Operators own certificate policy and private key material. App owners can see certificate state on domains and routes, but app-owner APIs and CLI output must never expose private keys, PEM material, ACME account keys, or storage paths.

Model

Certificate state is part of domain and route validation. A route can exist before it is active, but Via does not apply it to the public proxy until hostname policy, proof, approval, certificate coverage, route validation, and proxy apply all pass.

There are three common certificate paths:

PathUse whenWhat Via does
Public ACMEYou have a real DNS name and public ports 80/443.Obtains and renews publicly trusted certificates.
Internal CAYou need private, offline, or lab TLS.Issues certificates from a server-local root; clients must trust the exported root CA.
Uploaded certificateYou already have PEM material from another CA.Stores metadata and private key material server-side while keeping private material out of app-owner surfaces.

Public ACME

This is the normal production path:

via init --domain vary.example.com

The default [acme] mode = "prod" uses the public ACME flow. Certificate issuance can only succeed if the DNS name is yours and ACME validation can reach the host.

Requirements:

RequirementWhy it matters
DNS points at the Via hostACME must validate the exact hostname.
Ports 80 and 443 reach the hostHTTP/TLS challenges and HTTPS traffic need the public proxy.
The managed proxy is runningIssuance, renewal, and HTTPS serving depend on it.
Proxy state is writableAccount and certificate state must be persisted.

For a rehearsal that uses the real ACME flow without production rate limits:

via init --domain staging.your-domain.example --acme staging

Staging certificates are not publicly trusted. Use staging to test DNS, firewall, and renewal behavior before switching a real host to production mode.

Internal CA

Use the internal CA when the host is private, offline, or intentionally not using public ACME:

via init --domain via.internal.example --acme internal

Browsers, curl, and vary clients on other machines will reject internal-CA certificates until the client trusts that root.

Export the root on the Via host:

via certificate root-ca --output via-internal-root-ca.crt

For nonstandard state directories:

via certificate root-ca --state-dir /var/lib/via --output via-internal-root-ca.crt

Install via-internal-root-ca.crt in each workstation's operating-system trust store. This is client setup, not app deployment. Do not distribute private keys or ACME account keys. Only the root CA certificate is meant to leave the server.

Uploaded certificates

If you already have a certificate and private key from another CA, upload it through the operator API:

via certificate upload \
  --server https://vary.example.com \
  --session-token <operator-session-token> \
  --id apps-wildcard-2026 \
  --name "Apps wildcard 2026" \
  --source "private-ca" \
  --cert-file wildcard.apps.example.com.crt \
  --key-file wildcard.apps.example.com.key \
  --domain-names "*.apps.example.com,apps.example.com"

Inspect metadata without printing private key bytes:

via certificate list
via certificate inspect apps-wildcard-2026 \
  --server https://vary.example.com \
  --session-token <operator-session-token>

Uploaded certificates are server-side operator material. App owners should only see certificate IDs, status, validation errors, and readiness.

If the uploaded certificate chains to your own private CA, distribute that private CA root to clients through your normal endpoint-management process. Via's certificate root-ca command exports Via's internal root; it does not export roots for externally supplied private CAs.

Domain defaults

Use domain resources and policy to choose default certificate behavior for a hostname or suffix.

Add a domain whose routes inherit an uploaded default certificate:

via domain add apps.example.com \
  --scope app \
  --default-certificate-id apps-wildcard-2026 \
  --allowed-app-route-policy operator_approved

Patch an existing domain default:

via domain patch apps.example.com \
  --default-certificate-id apps-wildcard-2026

The default certificate ID is control-plane policy. App routes under that domain can inherit it, subject to hostname coverage and route validation.

Per-app and per-route certificates

When a specific app route needs a specific certificate, bind a certificate ID on the route through the operator API:

via route add shop.apps.example.com \
  --server https://vary.example.com \
  --session-token <operator-session-token> \
  --app shop \
  --service-port 8080 \
  --certificate-policy uploaded \
  --certificate-id shop-cert-2026

Patch an existing route:

via route patch <route-id> \
  --app shop \
  --certificate-policy uploaded \
  --certificate-id shop-cert-2026

Per-route certificates are useful when one app has a dedicated hostname, a customer-supplied certificate, or a certificate lifecycle that should not affect the whole suffix. The route remains blocked until the certificate covers the hostname and route validation succeeds.

For self-service app domains, app owners normally apply manifest routes during deploy:

vary app deploy shop --target prod --apply-manifest
vary app deploy shop --target prod --resume
vary app smoke shop --target prod

Renewal and operations

via doctor reports certificate renewal state through the certificate-renewal check. It reports the earliest expiry for the configured domain:

<domain> expires <ts> (in <N>d, authority=<...>, mode=<...>)

Default thresholds:

VerdictTrigger
INFOMore than 14 days to expiry.
WARN14 days or less to expiry.
FAIL2 days or less, or already expired.

Use Via certificate commands for operator metadata and lifecycle actions:

via certificate list
via certificate inspect <certificate-id> --server https://vary.example.com --session-token <operator-session-token>
via certificate rotate <certificate-id> --server https://vary.example.com --session-token <operator-session-token>
via certificate renew <certificate-id> --server https://vary.example.com --session-token <operator-session-token>
via certificate delete <certificate-id> --server https://vary.example.com --session-token <operator-session-token>

Route changes regenerate managed proxy config from control-plane state. Do not hand-edit generated proxy files; changes are overwritten by via init and by route updates.

← Domain proof token
TLS, routing, and the proxy →