Skyrsoft CertOps binds four network listeners, and only two of them are ordinary web targets a reverse proxy can terminate TLS on. What to publish, and how, is decided by your network topology rather than by anything configurable in the console — read this before you deploy.
How it works
A listener’s client-certificate requirement is set once, during the TLS handshake itself, before any request routing happens — so anything needing a different certificate posture from the rest needs its own listener, and anything sharing a posture is routed by path on the one it shares. That’s the entire reason there are four:
| Port | Protocol | TLS certificate | Serves | Proxy mode |
|---|---|---|---|---|
| Main HTTPS — configurable, 443 typical | HTTPS, HTTP/1.1 only | Your server TLS certificate, replaceable from the console without a restart | Everything except /agent/* |
L7. Re-encrypt or passthrough both work |
| 80 — configurable | HTTP | none | Redirects to HTTPS, except /crl, /ocsp, /scep and /.well-known/acme-challenge, which are served in the clear |
L7 |
| 5011 | HTTPS | Signed by CertOps’ internal Agent Identity CA — not publicly trusted | Agent bootstrap only | TCP passthrough strongly preferred |
| 5012 | HTTPS, client certificate required | Signed by the internal Agent Identity CA | All other agent traffic | TCP passthrough — mandatory |
Why the agent ports cannot be terminated
5012 is mandatory passthrough. The listener requires each agent’s actual client certificate at the TLS layer, in the initial handshake. A proxy can forward raw bytes, but it cannot re-present a client certificate it does not hold — terminate this port and agents cannot authenticate at all.
5011 should not be terminated either. It presents a certificate signed by CertOps’ own internal Agent Identity CA, which agents pin for themselves. Terminating it means importing that CA into the proxy and risking the pinning agents rely on.
Why two agent ports rather than one. Bootstrap never uses a client certificate and everything
else always does, so each port asks for exactly one posture in the initial handshake. Both are
HTTP/1.1 only, because domain-joined agents authenticate bootstrap and renewal with Kerberos.
Both agent ports are required. Agents bootstrap on 5011 and do everything else on 5012.
Publishing only one leaves agents able to do neither enrolment nor renewal. Both directions of
misrouting are refused outright with 421 Misdirected Request rather than being silently
mis-served: /agent/* reached on the main HTTPS port, and the console, the REST API or a PKI
endpoint reached on either agent port. A misconfigured proxy or a misrouted agent therefore fails
loudly instead of quietly.
What to publish
Grouped by who needs to reach it, so you can decide what goes on the internet and what does not.
PKI clients and automation
| Path | Auth | Notes |
|---|---|---|
/crl/… |
anonymous | CRL distribution points, per CA |
/ocsp, /ocsp/{caId} |
anonymous | POST with an application/ocsp-request body |
/ocsp/{requestBase64} |
anonymous | Base64 in the path — see the traps |
/.well-known/acme-challenge/{token} |
anonymous | Plain HTTP on port 80, for the public ACME CA to issue this server’s own certificate |
/.well-known/est/{slug}/… |
API key | EST (RFC 7030) |
/scep/{slug} |
SCEP challenge password | SCEP (RFC 8894) |
/api/v1/* |
X-Api-Key header |
REST API. Pro feature — returns 402 on Free |
Admin console — publish only if you want remote administration
The console is a good candidate for the management IP allowlist or a
tighter WAF policy. It comprises the single-page app and its assets, the /auth/* routes (login,
MFA, passkeys, SSO), the SSO callback, and the API the console itself calls. None of it needs to be
internet-facing for the PKI to work.
Things that catch people out
Don’t force /crl, /ocsp or the ACME challenge path to HTTPS. RFC 5280 and RFC 6960 both
expect these to be fetchable without first making a TLS trust decision, and many clients follow the
URL embedded in a certificate literally without honouring a redirect. CertOps builds every
certificate’s CRL and OCSP addresses from the Server FQDN over plain HTTP, and there is no
separate revocation hostname to point elsewhere, so whatever answers on that name must serve
/crl and /ocsp on port 80 without redirecting them, as CertOps’ own HTTP listener does. The
ACME challenge path must answer over plain HTTP because it has to work before the host has a
usable certificate.
Preserve the Host header. Two features derive behaviour from the hostname the browser used.
Passkeys compute their relying-party ID from it, so changing the public hostname invalidates
every passkey already registered. SSO builds its redirect_uri from it, and identity providers
match that byte for byte — every public hostname the console answers on must be registered at your
IdP.
Don’t negotiate HTTP/2 to the main port. That listener is deliberately HTTP/1.1 only, with no h2 ALPN, because Windows SCEP clients break otherwise.
Don’t let a WAF rewrite or buffer these bodies. OCSP POSTs are binary. EST enrolment bodies are
base64 PKCS#10. And /ocsp/{requestBase64} carries base64 in the path, so URL normalisation or
case folding corrupts the request. Backup downloads stream a large archive, so a low
response-size cap will truncate them.
Forward X-Forwarded-For — and then tell CertOps to trust it. CertOps records the connecting
address in the audit log, in agent registrations and in enrolment records, and it partitions the
login rate limiter by that address. Behind a proxy, every sign-in appears to come from one address,
so a per-attacker brake becomes a global one. The same applies to the IP block list and the
management IP allowlist. Forward X-Forwarded-For and
X-Forwarded-Proto, then enable them under
Settings → Security → Reverse proxy & client addresses: name the proxy’s address or network and
save. Changes apply to the next request — no restart, and nothing to edit on the server’s
filesystem. This is off by default and trusts nothing until you name the proxy, deliberately:
these headers come from the caller, so trusting them unconditionally would let anyone who can reach
the listener directly forge a source address. Headers from an address you have not named are
discarded, which means a wrong entry produces no error — just client addresses that keep reading as
the proxy.
A worked example
VS 1 L7 HTTPS <server-fqdn>:443 -> <host>:<main-https-port>
all paths except /agent/*
VS 2 L7 HTTP <server-fqdn>:80 -> <host>:80
redirect to HTTPS, EXCEPT /crl, /ocsp, /scep
and /.well-known/acme-challenge
VS 3 L3 TCP <server-fqdn>:5011 -> <host>:5011 passthrough
VS 4 L3 TCP <server-fqdn>:5012 -> <host>:5012 passthrough (mandatory)
Every virtual server sits on the one Server FQDN set in Settings → Server. It is the console’s name, the name SCEP, EST and the revocation endpoints use, and the address agents are given, so in a public cloud it has to be the public name every agent can reach. Each agent keeps the name it was installed with, so changing the Server FQDN later means re-installing or re-pointing the agents.
Limits
The REST API has no listener of its own. It is path-routed on the main HTTPS port, under
/api/v1/*.
Only /.well-known/acme-challenge is exempt from the HTTPS redirect, not the whole
/.well-known prefix. EST lives at /.well-known/est/* and authenticates with an API key, which
must never travel in the clear.
The main listener is HTTP/1.1 only, and that isn’t configurable, because Windows SCEP clients
break against HTTP/2. /scep is also exempt from the port-80 redirect: the Windows SCEP client sends
its GetCACaps/GetCACert probe over plain HTTP even when the configured URL is https://.
The two agent listeners can’t be merged into one, and bootstrap isn’t folded onto the main port. Keep both published.