Skip to main content
Version: Hortval 0.9.5 (unreleased)

Upgrading from an earlier 0.9.x

This page lists every configuration change between v0.9.1 and v0.9.5 that requires you to edit your config.yml, and the ones that change behaviour without saying so. Read it before replacing the binary.

The product was renamed in v0.9.5

Releases up to v0.9.4 were called Certeasy; from v0.9.5 the product, the command and the binary are hortval. This page uses the new name for present-tense behaviour and the old one when it names something you already have on disk. Nothing inside the ACME protocol carries the name, so your ACME clients are unaffected.

One thing makes an upgrade here different from most: the configuration file carries no version number, and it is never rewritten for you. There is no migration step and no compatibility shim. A key that no longer exists is not ignored — the file fails to parse and the server does not start.

Before any upgrade, back up the database, the installation directory and the configuration. Keep the database copy even for a release that does not seem to need one: it is the only part you cannot rebuild.

Check before you switch

The new binary can inspect your existing configuration without touching your deployment. Download it, put it somewhere temporary, and point it at the configuration the running service uses:

./hortval validate -f /etc/certeasy/config.yml

validate refuses exactly what serve refuses — that parity is guaranteed and tested as of 0.9.4 — so a clean run means the file will load. Nothing is written, no database is touched, and the running service is unaffected.

validate runs as you, the server runs as its service account

From 0.9.5 it also looks at the filesystem — does the working directory's parent exist, is an installation already sitting where a path resolves. That check answers "can I see this?", never "will the server be able to": you are typing the command, the service runs under another account. So a failure to read a directory is reported as a warning, not a verdict, and nothing checks writability — guessing another identity's permissions produces confident wrong answers.

validate --no-disk skips the filesystem entirely, for a config-only check on a machine that will not run the server.

Work through whatever it reports, then swap the binary.

Coming from 0.9.1

validate was introduced in 0.9.2, so your installed binary does not have it. Use the new one as shown above — that is the point of the check.

Configurations that no longer load

Each of these stops startup. validate reports them all.

Remove audit.rotate.max-backups

The key no longer exists, and a file still containing it is refused. Deleting an audit log by file count is not a setting: the log is a compliance artifact and the chain that makes it tamper-evident cannot survive an eviction. Retention will return measured in duration, which is the unit a compliance requirement is written in.

audit:
rotate:
max-size-mb: 100
max-backups: 10 # delete this line

This is the only configuration key ever removed.

Give every DNS validation profile a resolved-ip-policy

A profile with neither allow-cidrs nor deny-cidrs is refused. An empty policy accepted every address DNS returned, link-local and cloud metadata (169.254.169.254) included. Unset is not the same as deliberately open, so it is refused rather than defaulted.

dns-validation-profiles:
- name: internal
resolved-ip-policy:
allow-cidrs:
- "10.0.0.0/8"
- "192.168.0.0/16"

Use allow-cidrs with 0.0.0.0/0 and ::/0 if you accept any address on purpose.

This applies to every profile whatever challenge you use — challenge types are not restricted per profile, so a profile that has only ever served dns-01 still offers the paths this policy guards. Configurations produced by certeasy init, and the shipped examples, already carry the block.

Complete any fake authority

That driver used to parse its configuration without checking a single field, so bad values were accepted and failed later — after the database had been migrated — or produced a CA that expired the moment it was created. Now refused: an empty common-name, an empty password, a key-size below 2048, or a non-positive validity.

key-size must also be at least as strong as the strongest min-rsa-bits among the issuance policies bound to that authority. A CA weaker than the certificates it signs is a contradiction the server no longer accepts.

Remove explicit zeros

A 0 written deliberately used to mean "no limit" in places where it silently disabled a protection. It is now refused, and you get the default by omitting the key instead.

SectionKeys
serverread-header-timeout, read-timeout, write-timeout, idle-timeout, shutdown-timeout, max-body-bytes
rate-limitingany quota under a section left enabled: true
renewal-infowindow-width, retry-after

renewal-info.lifetime-fraction must additionally sit strictly between 0 and 1. Out-of-range values used to be clamped to two thirds without a word.

The server timeouts are worth a special look: before 0.9.4 their defaults were computed into a copy that was then discarded, so a deployment that never set them ran with no deadlines at all. Setting them correctly now is not a regression, it is the first time the setting takes effect.

Fix values that were never validated

KeyNow required to be
logs.outputstderr, stdout or file — and file requires logs.file
license.proxy-urla parseable http/https URL with a host
license.timeoutnon-negative
database.drivera driver that exists
authorities[].configuration.ca-name, .certificate-templatefree of control characters and colons, not starting with a dash
authorities[].configuration.certreq-path, .certutil-pathabsolute, or a bare file name

Most of these were caught eventually — at the first online licence check, or after the working directory and node marker had already been created. They are now caught before anything happens.

For the ADCS binaries, a bare name such as certutil.exe resolves from the Windows system directory and keeps working. A path relative to the working directory (tools\certutil.exe) is refused. See ADCS authorities.

Changes that do not stop startup

These load fine and behave differently. They are the ones worth reading twice.

Sections you filled in partially now work

Before 0.9.4, a configuration section was parsed into an empty structure, so writing part of a section set everything you did not mention to zero:

  • audit: with only path: meant enabled: falsethe audit log was off
  • rate-limiting: with only whitelist: meant every quota was zero — every limiter was off
  • database: omitted entirely meant a nil configuration, which failed a cold start

Defaults are now applied first and your values written over them. If you were affected, the upgrade turns these protections back on, and that is a change in behaviour on your deployment even though you edited nothing. Check whether your audit volume and your rate limits are what you intended.

Rate limit ceilings see more traffic

The per-IP ceiling now applies to every endpoint rather than four of them, so its defaults were raised to match: requests-per-minute from 200 to 1200, burst from 20 to 100. If you tuned these values down, revisit them — they now meter a much larger population of requests, and a single 3-name certificate costs at least 18.

account-creation moves from 5/hour burst 2 to 10/hour burst 10.

Log and audit files became naming bases

With rotation enabled, logs.file and audit.path name a series, not a file. Segments are written beside them as <name>.<UTC timestamp>.<discriminant>.<ext> and are never renamed.

Two consequences:

  • Point log collectors at the containing folder with a *.log pattern, not at the path in file. Existing logrotate rules on logs.file become inert.
  • Remove any logrotate rule targeting audit.path. External rotation of the audit file is no longer supported at all: a third party renaming or truncating it breaks the tamper-evident chain, and the server cannot tell that apart from tampering. Earlier documentation recommended copytruncate here; that recommendation was wrong and is withdrawn.

While you are editing these two keys, write them as absolute paths. A relative one resolves against the process working directory, not against workdir — this page's predecessor claimed otherwise. See Configuration overview for what that costs on a Windows service.

logs.rotate.max-backups also changed default from 0 to 5. At 0 — the old default — enabling rotation discarded history at every turn.

Your ADCS connector may have changed underneath you

From 0.9.2, type: adcs means the native connector: in-process COM, no certreq.exe, no child process. Configurations naming adcs were switched over without a word at upgrade time.

If you need the old behaviour — an EDR policy that only trusts the signed Microsoft binaries, for instance — say so explicitly:

authorities:
- name: corp-ca
type: adcs-cli # was the meaning of `adcs` before 0.9.2

adcs-native is available as an explicit spelling of the new default. See Antivirus and EDR for which one suits your host.

In the same release, default-timeout started being honoured. A bug meant it was overwritten with 30 seconds whenever cert-util-timeout was unset, which was the normal case; requests that used to be cut off at 30 seconds now run to the configured value, or to the 4-minute default.

Moving to 0.9.5

Everything above still applies — 0.9.5 adds no new keys and removes none. What it changes is the name, and where paths are allowed to point.

You can still go back

The symmetry noted at the top of this page stops at 0.9.4. A configuration fixed for 0.9.5 still loads on 0.9.4, provided you wrote absolute paths — which is what the startup message hands you, ready to paste. Only %CONFIGDIR% closes the door, and it appears solely in configurations generated by hortval init, that is on fresh installs, which have nothing to roll back to. No schema migration ships in this release either.

Worth knowing before you upgrade, not after: the moment you want a rollback is rarely the moment you want to discover whether one is possible.

The command is hortval

Update every invocation: the ExecStart= of a systemd unit, the binPath= of a Windows service, your runbooks, your monitoring. Your config.yml keeps its name and every key it has.

Every path must be absolute or anchored

A relative path is now refused at startup and by validate, in all eight settings that name one: workdir, database.path, audit.path, logs.file, local-pki-cache-dir, letsencrypt.cache-dir, and a bundle's local-cert-file / local-key-file.

Write an absolute path, or anchor it: %WORKDIR% in any of them except workdir itself, which takes %CONFIGDIR% — the directory holding the configuration file.

workdir: "%CONFIGDIR%/workdir"
database:
path: "%WORKDIR%/db.sqlite"

The refusal names the setting, and when a live installation sits where the value resolved, it prints the exact line to write.

This one catches more deployments than it looks. A configuration generated by the 0.9.4 wizard has workdir: ./workdir, and the shipped examples used .workdir — the documented onboarding path produced exactly the file that now fails.

workdir is required, and there is no default

Set it to an absolute path. A configuration that omits it does not start.

workdir: 'C:\ProgramData\hortval' # or /var/lib/hortval

The reason is the same one behind the rule above: this key decides where the database, the node identity, the audit log and the CA key live, and that is not something the binary should pick for you. It was the only path setting still allowed to be guessed.

The refusal tells you where your data is. Until v2, startup looks at the locations previous releases used — including the pre-rename certeasy ones — and names any installation it finds, with the exact line to paste. If both hold one, it says so and lets you choose. Nothing is moved for you.

An installation is recognised by its server_id marker. A directory holding only a logs/ folder is not one: a service that started once and failed leaves exactly that.

Configuration is no longer looked for in the current directory

It never should have been: running a command from any directory a less privileged account could write to loaded that account's configuration, and the configuration selects the database, the working directory, the audit destination and the outbound proxy. Two candidate files is now a startup error too, rather than a silent first-wins.

Pass -f <path> and neither concerns you. The certeasy configuration directories are still read, with a warning, until v2.

Per-service log levels: two names changed, and an unknown one now stops startup

If you set logs.services, check it. Two services were renamed, dropping the product name they carried:

BeforeNow
Certeasy-acme-serveracme-server
cert-easy-mainmain

There is no alias. A configuration carrying an old name is refused at startup and by validate, and the message lists the accepted names.

That refusal is the point. Until 0.9.5 an unrecognised key was silently ignored — the level you asked for quietly fell back to the global default, and the only symptom was logs quieter than you expected, at the moment you were trying to diagnose something. A key that never applies is worse than a missing one, because nothing distinguishes it from a working setting.

The examples on Logging used the old name, so a configuration copied from that page will not start. One edit, once.

Two more, easy to miss

  • Allow egress to api.hortval.com. The licensing backend moved. If your egress is filtered by hostname, an upgrade without this slides the deployment into its degraded licence states.
  • A letsencrypt bundle now always serves ECDSA P-256. Before, the first client to connect decided the key type for everyone. If a client of yours needs RSA, use mode: pki or mode: files, where key.type is yours to set.

What changed in which release

0.9.1 → 0.9.20.9.2 → 0.9.30.9.3 → 0.9.40.9.4 → 0.9.5
Keys removedaudit.rotate.max-backups
Values now refusedzone protocol11 groups, see aboveevery relative path, unknown logs.services keys
Behaviour changedadcs connector, default-timeoutpartial sections, rate limits, rotationcommand name, workdir now required, config lookup, licence host, Let's Encrypt key type
Keys added7, all optional5, all optional

0.9.2 → 0.9.3 requires no configuration change. It only added optional keys: disable-ca-revocation, the certificate manager's key block, and a fake authority's certificate-validity.

The one break in 0.9.2 is dns-validation-profiles[].zones[].protocol: a value other than udp, tcp or empty was parsed and then ignored, and is now refused. Note also that tcp came to mean TCP with no UDP fallback, where before the setting did nothing at all.

After the upgrade

Hortval applies additive database migrations on its own at startup and refuses anything that a rollback could not undo, naming what is pending. Nothing in the 0.9.1 → 0.9.4 range is affected — every migration shipped so far is additive — but the contract, the exit codes and the noddl workflow are described in Migrations.

For the full narrative of each release, see the Changelog.