How it reaches the wire

Cert Camel

Cert Camel obtains TLS certificates from a certificate authority and installs them on HAProxy load balancers. This page follows one certificate the whole way — from the list of names you asked to be watched, to bytes a browser receives — and explains each decision made along the route.

Six stages, six files

Nothing is passed in memory. Each stage reads a file the previous one wrote and writes a file the next one reads. Every stage can also be run on its own, which is why the handoffs are files rather than function calls.

domains.txt ssl-data.js sweep verdict certs\<certId>\ check-ssl renew-due renew deploy HAProxy node probes each host groups into certificates orders what is due pushes Data Plane API
Reading a stale file looks exactly like reading a fresh one. Because of that, renew-due re-runs the checker whenever domains.txt is newer than ssl-data.js, rather than working from a host list that has since changed.
StageReadsWritesWhat it decides
check-ssldomains.txtssl-data.js What each host is serving today, and when it expires
renew-duessl-data.jssweep verdict Which certificates exist, and which are due
renewsweep verdictcerts\ Obtains the certificate from the authority
deploycerts\deploy record Puts it on each node and proves it is serving

check-ssl connects to each host on the port domains.txt names — host:port, defaulting to 443 — and records what is being served now. That is how a certificate obtained somewhere else is still watched, and how expiry is known for something Cert Camel has never issued.

Hosts become certificates

You list hostnames. A certificate authority issues certificates, each covering one or more names. Turning the first into the second is the decision everything downstream inherits.

Hosts are grouped by DNS zone, because a zone is what one DNS credential can prove control of. Each zone produces one to three certificates, and each gets an identifier — its certId — which becomes its folder name, its file name, and the name of the crt-list it goes into.

watched, zone example.com example.com a.example.com *.example.com console.example.com splits the zone's SAN certificate certId example.com covers a.example.com the zone's wildcard certificate certId wildcard.example.com covers *.example.com example.com the console's own certificate certId console.example.com covers console.example.com example.com moves here its own folder, its own crt-list, its own renewal date never shares a crt-list with the SAN certificate installed nowhere — it serves the console itself
The bare domain belongs to the wildcard whenever a zone has one. *.example.com does not match example.com, so a wildcard certificate must carry the bare name too. If the SAN certificate carried it as well, both would answer to it with equal claim, HAProxy would pick one, and the other would silently never be used.

Why the console is separate

Cert Camel serves its own page over HTTPS with a certificate it obtains like any other. Keeping it on its own means adding or removing a production hostname never re-issues it, and it is never installed on a load balancer, so its name does not travel anywhere nobody asked it to go. See The console certificate.

Certificates somebody else renews

A certificate can be marked managed elsewhere. It is still watched — so you find out when whatever renews it stops — but Cert Camel never orders it and never counts it as a gap in its own coverage. Issuing a second certificate for a name another system is already renewing is a good way to have two systems fighting over one name.

When it renews

A sweep runs on a schedule and decides, per certificate, whether to renew now. It asks the certificate authority rather than counting days: authorities publish a renewal window per certificate (ARI), which spreads renewals out instead of stacking everyone on the same threshold.

each certificate did this authority issue it? yes no renew once the authority's window opens a date it publishes per certificate renew only within 30 days of expiry otherwise leave it alone
The second branch is why a first run does not reissue everything. A certificate obtained elsewhere is watched from the moment it is listed, but only taken over when it is genuinely close to expiring — so adding twenty hostnames does not order twenty certificates that night.
Rate limits are why this is careful

Let's Encrypt allows 50 certificates per registered domain per week, and only 5 identical certificates per week. A loop that reissues on every run burns the duplicate limit in an afternoon and then cannot renew anything for seven days. Asking the authority when it wants the next renewal avoids the question entirely.

Where files land on a node

Certificates reach HAProxy through its Data Plane API, a REST interface that writes files and reloads configuration on the operator's behalf. Two of its behaviours shape everything here.

The API chooses the directory. A file is uploaded by name, not by path, so a path pointing anywhere else describes a file that will never exist. The directory is discovered from the node rather than typed — which is why the crt-list setting offers a structure and fills the directory in for you.

The API rewrites names. Dots in a filename become underscores, so example.com.pem is stored as example_com.pem. Anything referring to that file afterwards — a crt-list entry, a bind line, a later lookup — must use the stored name, not the requested one.

ONE LIST FOR EVERY FRONTEND SAN cert wildcard cert split off crt-list.txt wildcard-crt-list.txt two files, however many domains you renew ONE LIST PER PARENT DOMAIN any certificate <certId>-crt-list.txt already separate — a wildcard has its own identifier
A crt-list is a file listing certificates a frontend may serve. Referencing certificates through a list rather than one by one means a new certificate starts being served by adding a line to the list, with no change to the HAProxy configuration itself.

Why a wildcard is always kept apart

Anything reading one list can serve everything in it. Putting a wildcard beside the certificates it overlaps means a frontend intended for one can serve the other, and the first entry in a list is what an unrecognised request falls back to. Separate files keep that choice explicit.

StructureSAN certificateWildcard certificate
crt-list.txtcrt-list.txtwildcard-crt-list.txt
{certId}-crt-list.txtexample.com-crt-list.txtwildcard.example.com-crt-list.txt
A group answers "which nodes", a crt-list answers "which file"

A deployment group holds the nodes and the credentials to reach them. A certificate is assigned to one or more groups, and the crt-list setting on the group decides the filename inside each. A single certificate can be pinned to a different file on one group than on another; an explicit per-certificate override is left exactly as written, because it is already a deliberate answer.

Proving it is really serving

An API returning success means the file was accepted, not that anything is using it. So each node is checked afterwards, in tiers.

TierQuestionWhen
T0Is the bundle valid at all?before anything is pushed
T1Did the node's API accept it?per node
T3Is the node actually serving it, by serial?per node, per name

There is no T2. "Present in HAProxy's memory" is covered implicitly: the Data Plane API pushes to the runtime socket and only falls back to a reload if that fails, so a node passing T3 has necessarily loaded it.

upload T1: API accepts it crt-list create or append ask the process does it know this list? yes nothing reads it T3 probe serial must match serving failed awaiting a bind line Cert Camel never edits HAProxy configuration. It states the line to add: bind <address>:443 ssl crt-list /certs/example_com-crt-list.txt alpn h2,http/1.1
A serial number is the proof. Each issuance carries one, unique to it, so comparing what a node serves against what was just obtained distinguishes the new certificate from the one it replaced — which a name or an expiry date cannot.

How a wildcard is proved

A wildcard cannot be proved by asking for the name it covers, because several certificates may answer to that. It is probed with a synthetic certcamel-probe.<apex> that only a wildcard can match. The bare domain is probed too, but only for coverage — it says who is serving a name more than one certificate may legitimately hold.

Three outcomes

OutcomeMeansDo
serving The node is answering with this exact certificateNothing
awaiting bind Installed, but no configuration points at it yet Add the bind line, reload HAProxy
failed Rejected, or serving something else Read the log — the node said why
Waiting for a bind line is not a failure

HAProxy refuses to load a configuration naming a certificate file that does not exist. A brand-new frontend therefore has to be built in two steps: put the certificate on the node first, then add the configuration pointing at it. A deployment that ends here has done everything asked of it, so it reports success, sends no failure alert, and hands over the line to add. A node already proved to be serving is never reported this way, even if some other list is unread — the state means nothing could be verified, not that a file is unreferenced.

Where two parts must agree

Four values are produced in one place and consumed in another. Each is a plain string, so a mismatch is silent: nothing fails, the two sides simply describe different things.

ValueProduced byConsumed byIf they disagree
The certificate identifierthe grouping, from the watched hosts the folder, the filename, the crt-list name, renewal a certificate is filed under a name nothing looks for
The crt-list pathdeployment, from structure + identifier the load balancer page's reporting a working deployment is reported as never going to be served
The stored filenamethe node's API, after rewriting dots crt-list entries, bind lines, later lookups a bind line names a file that does not exist
The deployment outcomeone deployment run a log, an email, an audit entry, two pages the same event is green in one place and red in another
The identifier is derived, not stored

A certificate's identifier is computed from the hosts being watched, so it can change when the watched set changes — giving the console its own address, for instance, moves its name onto a certificate of its own with a new identifier. Anything holding an identifier from before must re-derive it rather than remember it.