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.
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:
| Path | Use when | What Via does |
|---|---|---|
| Public ACME | You have a real DNS name and public ports 80/443. | Obtains and renews publicly trusted certificates. |
| Internal CA | You need private, offline, or lab TLS. | Issues certificates from a server-local root; clients must trust the exported root CA. |
| Uploaded certificate | You already have PEM material from another CA. | Stores metadata and private key material server-side while keeping private material out of app-owner surfaces. |
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:
| Requirement | Why it matters |
|---|---|
| DNS points at the Via host | ACME must validate the exact hostname. |
| Ports 80 and 443 reach the host | HTTP/TLS challenges and HTTPS traffic need the public proxy. |
| The managed proxy is running | Issuance, renewal, and HTTPS serving depend on it. |
| Proxy state is writable | Account 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.
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.
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.
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.
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
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:
| Verdict | Trigger |
|---|---|
| INFO | More than 14 days to expiry. |
| WARN | 14 days or less to expiry. |
| FAIL | 2 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.