Skip to content
Download free

Skyrsoft CertOps · Run it securely

Architecture, ports and reverse proxies

The four listeners Skyrsoft CertOps binds, why two of them cannot be TLS-terminated by a proxy, what to publish, and the things that catch people out.

All 25 CertOps features · Run it securely

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.