The VM answered pings, so it was on the network. Only SSH said Connection refused, and CPU load sat at zero. No panic, no full filesystem. A machine alive enough to answer packets but not alive enough to let you in is the most unpleasant kind of failure. What came before it was an ordinary reboot.
It had started out friendlier. On 18 August net/freeipa-server landed in the official FreeBSD ports tree, commit 35e4879, and a day later the update to 4.13.2 followed. FreeIPA is not a program, it is a federation: 389 Directory Server for LDAP, an MIT Kerberos KDC, Dogtag PKI as the CA, and an Apache with mod_wsgi in front of it. Four services that have to come up together and go down together.

Eight bugs came out of this, and where they turned up says more about porting work than the bugs themselves. Software counts as ported once it starts. That is where the log ends. Half of my findings were behind that point, at the reboot, at the shutdown and at the upgrade.
What lay before the first reboot
Fair is fair, four bugs sat before those three points, and two of them are not even mine, they belong to sssd2. autoconf does not replace @datadir@ with a path, it replaces it with the shell reference ${prefix}/share. A Makefile resolves that, a Python file never does, and the port passed neither --datadir nor --sysconfdir:
[Errno 2] No such file or directory: '${prefix}/share/sssd/sssd.api.conf'
It hit sss_obfuscate from the port itself as well. The fix went to arrowd@ as PR 297667 and is committed. Next to it the little brother: the @sample entry in the pkg-plist carried no mode, so pkg created sssd.conf as 0644, and sssd then refuses to start and says Can't read config: 'File ownership and permissions check failed'.
After that FreeIPA stood up. The ticket arrived, LDAP answered, the web UI was reachable, only GSSAPI login was not. The browser got a terse No valid Negotiate header in server response, which sends everyone to the service principal first. The httpd log was more honest:
GSS ERROR gss_acquire_cred[_from]() failed to get server creds:
[No credentials were supplied ... SPNEGO cannot find mechanisms to negotiate]
No credentials. Not the wrong ones, none at all. The HTTP keytab was owned by root. The port deliberately runs with GSS_USE_PROXY=no, because gssproxy’s S4U2 delegation is unreliable inside the long-lived httpd worker, and whoever has no broker has to reach for the material himself. httpd runs as www. A Kerberos problem that was a question of file permissions.
On top of that, three ACME templates shipped by the port carried a hardcoded Linux path, Unable to start ACME engine: Cannot find config file: /etc/pki/pki-tomcat/ca/CS.cfg, while the instance here lives under /var/db/pki/pki-tomcat/conf.
The reboot
Back to the VM. Without SSH all that is left is the screen, and the QEMU monitor hands it over as a screendump. On it was one line that changed only in a single digit:
Error connecting to bus for "com.redhat.oddjob" (attempt 17)!
The counter was running, the system was not. dbus had not been enabled. It is not missing on FreeBSD, it is an ordinary rc service and off by default, while on Linux the system bus is always there through socket activation. certmonger therefore never started at all, oddjobd kept retrying in a blocking loop, at a point in the startup chain that comes before sshd. That is why the box was on the network and mute at the same time: the kernel had brought the interface up long ago, userland never got as far as a shell. I got in through single user mode, driven by sendkey on the QEMU monitor: type blind, pull an image, check whether the letter arrived.
The second finding was quieter. /etc/rc.d/kadmind defines no command of its own, it lives entirely off kadmind_program from rc.conf, and the port’s disable path had removed that variable. So service kadmind onestatus bravely reported kadmind is not running while the process was happily alive. ipactl status took that at face value, ipactl restart waited forever. The service was fine, only rc(8) knew nothing about it, and everything above rc(8) believes rc(8).
The shutdown
Hardly anyone tests the shutdown, it seems to take care of itself. With Dogtag it did not. Tomcat wedged, Waiting for PIDS sat on the console for minutes, and because ipa-server-upgrade wants to stop the instance first, the stuck process dragged the upgrade down with it. A kill -QUIT on the JVM then produces the thread dump:
"main" java.lang.Thread.State: BLOCKED (on object monitor)
at org.mozilla.jss.ssl.SSLSocket.close
at com.netscape.cmscore.ldapconn.LdapBoundConnFactory.shutdown
at org.dogtagpki.acme.server.ACMEEngine.shutdownRealm
The main thread wanted to close the LDAP connections of the ACME engine, the very same one that could not find its configuration earlier, and got stuck on a monitor. That monitor was held by LDAPConnThread-8, sitting in a blocking socket read. Underneath lay a second, independent nastiness: jsvc writes the pid of its child process into the pidfile, while it is the parent that reacts to SIGTERM. rc(8) therefore signals the wrong one with great reliability and then waits without a timeout. The port now brings its own stop path, sixty seconds of grace, then hard.
The upgrade
With 4.13.2 half the substructure came along: dogtag-pki from 11.7.0 to 11.10.1, dogtag-jss from 5.7.0 to 5.10.1, dogtag-ldap-sdk from 5.6.0 to 5.7.0, resteasy from 3.0.26 to 3.15.6, and Dogtag now runs on Java 21. On the side, the hardcoded samba416 dependency is gone, the port uses the samba framework of the ports tree, patch courtesy of fluffy.
Dogtag 11.10.1 reorganised its source tree in the process, and two FreeBSD adaptations that had worked cleanly before fell through the cracks: ModuleNotFoundError: No module named 'selinux' and FileNotFoundError: [Errno 2] No such file or directory: 'runuser'. runuser comes from util-linux and will never exist here, the privilege switch now goes through subprocess. Not new construction sites, but old ones torn open again by somebody else’s refactoring: the quiet rent you pay for a port of this size.
Anyone who already runs a server has two things to do by hand when updating, the pointer to the JDK (Java 21) and the ACME paths. Both concern an already deployed pki-tomcat instance whose configuration no package may touch. Without the first, Tomcat stops with UnsupportedClassVersionError: class file version 65.0 and ipa-server-upgrade aborts. Both steps are spelled out in the ports UPDATING entry of 19 August 2026.
The bug that was none
Somewhere in between the machine lost its own name, so thoroughly that even ipactl bailed out with socket.gaierror: [Errno 8] Name does not resolve. Not a port bug, but one with reach, because it hits every FreeBSD guest with cloud-init enabled: it rewrites /etc/hosts on every boot and throws out the line carrying the FQDN and the real IP. A drop-in with manage_etc_hosts: false under cloud.cfg.d did not help, because Proxmox generates the user-data itself, with manage_etc_hosts: true, and user-data outranks everything from cloud.cfg.d. The only reliable answer is touch /usr/local/etc/cloud/cloud-init.disabled.
Why only now
The obvious guess would be that this was the first time anything got set up from scratch. It was not, the test VM is a consumable, it gets thrown away and recreated all the time. What had moved was the substructure, sssd sits at 2.13.1, Dogtag at 11.10.1, and every fresh version brings fresh assumptions about what an operating system looks like. Above all, though, the whole chain ran end to end for the first time: install, reboot, shutdown, upgrade. Four of the eight findings sat at those transitions, and they are the four that appear in no installation log.
Where the server stands today
Better than I expected. On a fresh machine it installs cleanly, comes back up on its own after a reboot and shuts down properly. Kerberos, LDAP, GSSAPI over HTTP, the web UI, the CA and the resolution of IPA users through SSSD all work. I still would not recommend it for production: the client port in the tree does not carry the FreeBSD adaptations yet, a separate PR is pending for that, and I could not verify ipa-getcert for lack of DNS. As long as that is open, the server belongs on a VM you are allowed to throw away.