iRonShore Technologies / Lighthouse docs Version 0.37.0

Running the iRonShore Lighthouse server

lh serve is the whole server: the dashboard, its JSON API and the job runner that scans and syncs CAs on a schedule. On a Windows server it runs as a Windows service with HTTPS and Windows sign-in, so people open it from their own PCs.

What you need

  • A Windows server that can reach the SQL Server database, the networks to scan and the CAs to sync.
  • A service account: a group managed service account (gMSA, recommended), a domain user account with a password, the built-in virtual account NT SERVICE\Lighthouse (which reaches other machines as the server's computer account), or, on a server outside a domain, a local account. The service account compares them and gives every step and right for each. It needs:
    • db_datareader and db_datawriter on the Lighthouse database (schema changes are made by the installer as the person installing, or with lh db migrate);
    • Read on each ADCS CA to sync, and, to issue from it, Request Certificates on the CA plus Enroll on each certificate template Lighthouse uses (Issue and Manage Certificates on the CA too, to revoke);
    • read access to the private keys of the HTTPS certificate and the EJBCA client certificate.
  • For each EJBCA CA, a client certificate whose EJBCA role allows what Lighthouse does there. To read (EJBCA sync): view certificates and end entities, and the CAs to read. To enroll through EJBCA templates, the role also needs /ca_functionality/create_certificate and the rules to create and edit end entities, plus the template's CA and end entity profile; /ra_functionality/revoke_end_entity to revoke. A certificate made only for reading is usually refused with HTTP 403 at the first enrollment. EJBCA cannot report a certificate's rights ahead of time, so neither the installer nor Test connection can check this; set the role up in EJBCA before the first enrollment, step by step in Giving Lighthouse's certificate the rights to enroll. Turn on REST Certificate Management and REST Certificate Management V2 under EJBCA's System Configuration, Protocol Configuration.
  • An HTTPS certificate in LocalMachine\My, for example from the internal CA's Web Server template. Its SAN must hold every name and address people type in the browser, including any address a firewall forwards to the server.
  • Two domain groups, for example Lighthouse Viewers and Lighthouse Operators.

Roles

Who Can
Members of --operators (administrators) Everything: jobs, owners and teams, locations, templates, enrollment, settings
Members of --key-recovery Recover kept private keys of certificates they can see
Members of --viewers See every certificate someone owns, with the CAs, locations and jobs behind them; export CSV; download certificates. No templates unless a template names them
Members of a team See and work on that team's certificates, locations and jobs (see Teams below); requesters and operators also see the templates that list the team (see Who sees which templates)
People and groups named on a template See that template on Enroll and enroll through it for themselves
People and groups given a profile on People & Access What the profile allows, everywhere or for the teams named (see People & Access below)
Anyone else Nothing (HTTP 403), unless New people gives them something; they still appear on People & Access

Nobody but an administrator sees more than this: lists of teams, CAs, tags and change windows show a person only the ones behind certificates they see, and every count is over what they see.

The three groups always apply, so an administrator can never be locked out from the console. Every change and every run is in the audit log with the user's name.

Certificate Authorities

Settings > CAs lists every CA Lighthouse knows. Administrators add, change and remove CAs there, which is the step after a new install and the place to go when the organization changes CAs.

  • Add CA takes the kind (Microsoft ADCS, EJBCA, DigiCert CertCentral or HashiCorp Vault PKI; with More public CAs on, also any ACME CA, Sectigo Certificate Manager and GoDaddy, see More public CAs), a name, and how to reach it: the config string HOST\CA Name for ADCS; the URL, client certificate thumbprint and optional CA name for EJBCA; the API key for DigiCert; Vault's address, PKI mount and a token or AppRole for Vault (see HashiCorp Vault PKI). API keys, tokens and secret ids go to the secret store and are never shown again. The name is how certificates and templates refer to the CA.
  • Test connection connects from the server with the settings in the dialog, saved or not, and reads the first issued certificate. It changes nothing on the CA.
  • Each CA is read by a job of its kind (Read <name>, daily at 06:15 UTC unless you choose otherwise), which the page keeps in step; Read now runs it. A CA first read by a sync job added under Discovery shows that job, and editing the CA keeps it.
  • Bring in (ADCS and EJBCA) chooses which certificates the read takes. Everything the CA issued is the default and how it was before. Only certificates from these templates limits it to the ticked ADCS certificate templates or EJBCA certificate profiles. The list shows what the CA publishes (or the profiles its end entity profiles allow) and the templates its certificates in Lighthouse came from, with how many; a name can also be typed. ADCS keeps a newer (version 2 and later) template as its OID; Lighthouse shows it by name when the CA still publishes it, and matches either. EJBCA is searched once per profile, so only those certificates come over, and each one's place names its profile, which the template: rule term then matches. Skip certificates that have already expired leaves out old ones (ADCS asks the CA only for those still valid). Revoked certificates were never read. Saving changes nothing already in Lighthouse; the read's summary says how many it left out.
  • Remove them appears on the CA once a read has used the current Bring in setting and some certificates from earlier reads no longer match. The dialog first says how many there are and how many it would take; pressing it asks again. It takes only certificates found nowhere but this CA, with no owner, Facts, tags, kept key or waiting request, and not issued through Lighthouse (enrolled, or refused after the CA issued it); the rest stay. Each one removed is written to the Keeper's Log as certificate-left-out with what it was, and the press as ca-left-out-remove. Unlike the Cleanup, nothing is kept out for good: if Bring in changes to include them, the next read brings them back. One press takes at most 10,000; press again for more. lh sync adcs and lh sync ejbca take the same choice as --template <name> (repeat it) and --skip-expired.
  • Turning a CA off keeps its certificates and its history. It is no longer read, and templates that issue from it refuse new certificates until it is on again or the template is pointed at another CA. Use this for a CA you are moving away from.
  • Remove is only for a CA with no certificates and no templates, such as one added by mistake; it removes its job too.
  • The template dialog's Fill in from a CA copies an EJBCA CA's address and client certificate, a DigiCert CA's secret name, or a Vault CA's address, mount and sign-in, into the template. For ADCS it lists the certificate templates the CA issues; for EJBCA it lists the CAs and end entity profiles, and picking an end entity profile lists the certificate profiles and CAs that profile allows (see EJBCA profiles).

Lighthouse words

Lighthouse gives some things its own names. The console shows plain words beside them: in brackets after the heading, as a tooltip on the tab, and on the Settings cards. Help has the same list under Lighthouse words. Only on-screen labels have hints; page addresses, the API and rule words are unchanged.

Lighthouse name Plain words
Spotlights saved searches
Facts custom fields
Runway expiry outlook
Installations install targets
Pipelines event rules
Move import and export
Outposts agents
Keeper's Log audit log
Profiles, Grants roles, who has which role

The Settings group for Replace many (drills and CA migration) and Move is Bulk changes (it was Big jobs); its address is still #bigjobs.

Who looks after Settings

Administrators (people, groups or API tokens with the whole Administrator profile everywhere, the ones GET /me calls Operator and the signed-in panel calls Administrator) open all of Settings. Since 0.30.0 each group under Settings also has a permission of its own, so someone can look after one part without being an administrator of everything:

Permission Opens
Look after Connections (settings.connections) Settings, Connections: CAs, templates and policies, facts, key storage, integrations and Outposts
Look after People (settings.people) Settings, People: every team, People & Access, sign-in and notifications
Look after Bulk changes (settings.bulk) Settings, Bulk changes: Replace many and Move
Read the logs (settings.logs) Settings, Logs: the Keeper's Log and its check, and the Server Log with what it records
Look after the Server (settings.server) Settings, Server: application settings, Features, license and cleanup, and the Dashboard layout for everyone

They apply everywhere only. Put one or more in a profile (Copy a built-in one) and grant it. Someone who opens no group does not see Settings in the rail, a Settings address says which permission it needs, and the API answers 403. Needs an owner, Spotlights and who may use them, and Pipelines stay with administrators. Change settings, Manage access and Manage templates are gone: they opened nothing on their own since 0.19.0, and profiles that still hold them drop them when read.

Settings is five groups, each a place under Settings in the rail with its pages as tabs, and every Settings page is in exactly one of them. Settings itself opens a page with a card for each group you open. Page names are unique, so "Settings, Integrations" still names one page.

Group Tabs
Connections CAs, Templates & Policies, Key Storage, Integrations, Outposts
People Teams, People & Access, Sign-in, Notifications
Bulk changes Replace many (drills and CA migration), Move
Logs Server Log, Keeper's Log
Server Application, Features, License, Cleanup

Teams is open to everyone for their own teams, so people who are not administrators see Teams as a place of its own in the rail. Certificates nobody owns are not a place in the rail: they are the administrators' by default and wait in the built-in Needs an owner Spotlight on Certificates, for administrators only (see Needs an owner).

What people need to do their work stays outside Settings and open to them: enrolling reads the templates, facts and CAs open to their teams; the banner and the Features switches are read by every page; and the API Explorer is API next to Help at the top of every page (it is no longer a Settings tab). The Server Log and kept keys are records rather than settings: GET /api/v1/logs and /keys also answer whoever holds See the server log or See kept keys. The Keeper's Log is a tab of Logs under Settings; administrators, people who open Logs, and anyone with Read the Keeper's Log everywhere (the built-in Auditor profile has it) read it, in the console and with GET /api/v1/audit. An auditor sees it in the rail without opening any other part of Settings. A SIEM gets a copy through log forwarding.

People & Access

Administrators and people who look after People open People & Access under Settings, People.

  • Permissions are single things a person may do, such as Approve requests, Revoke, Run jobs, Read the Keeper's Log or Look after Connections. Some can be limited to teams; the configuration ones (the five Settings permissions, Manage teams and so on) apply everywhere only.
  • Profiles are named sets of permissions. Built in: Administrator (everything except recovering keys), Read-only, Requester, Certificate manager, Team lead, Approver, Auditor (also reads the Keeper's Log) and Key recovery. Built-in profiles cannot be changed; Copy one to make your own.
  • Grants give a profile to a person or group, everywhere (including certificates no team owns) or only for some teams (what those teams own, their jobs and requests made for them), and optionally until a date: the grant ends at the end of that day, UTC. A person or group is written EXAMPLE\name or [email protected] (a Windows account or group, an email or a UPN). For other sign-in providers, group:<name or id> matches a groups or roles claim and user:<id> a user's subject. A person or group has one grant of each profile, whatever the case of the name: granting it again (say example\devuser after EXAMPLE\devuser) adds to the grant already there, and any such pairs saved before are merged when People & Access is opened.
  • You give only what you hold. A grant, a profile, team members, a Spotlight's Who can use it, Make their own automation, a token's profile and New people all check that the person saving them holds what they give, where they give it; removing or narrowing access needs what it gives now. A change that names you (your account, a group you are in, a team you are in) waits on Requests for someone else who looks after that part of Settings; the console says so. On a lab with one administrator, turn on Administrators may approve their own requests under Settings, Application, General to approve it yourself. Giving someone the Administrator or Key recovery profile is announced to the administrators. Because the Administrator profile does not include recovering keys, only someone who holds Recover kept keys gives Key recovery.
  • People who have signed in lists everyone Lighthouse has seen, how they signed in and what they could do then. Someone new with no access appears here as No access; Grant… gives them a profile.
  • New people sets what someone gets when no group, grant or team gives them anything: nothing (the default), or a profile for the certificates they own themselves or everywhere. A profile with Look after People cannot be chosen.
  • Make their own automation lists the people, groups and teams who may make their own automation (see Who makes automation), each optionally until a date. Nobody is on it at first.
  • Check access shows what a person, group or token may do and every grant, group, team, shared Spotlight and template that gives it. Lighthouse looks a person up in Active Directory for their groups when it can; add groups to see what someone would get in them. A certificate's details have Who can see this, and Reports has Who has access, for the same people.
  • Safeguards: Recovering a kept key needs a second person (see Keeping private keys; administrators only change it), and the monthly Still needed? email: access that ends within 30 days, every administrator, everyone who may recover keys, and people and tokens not seen or used for 90 days (30 to 365), sent to Settings, Notifications' default recipients or the addresses given. See this month's list shows it now.

Alerts are for administrators unless an administrator says otherwise. Alerts are the Certificate expiring and Certificate weak Pipelines (see Alerts). See alerts opens Pipelines with only those two events and shows what was told (everywhere: every notice and run; for some teams: notices about those teams' certificates). Create and change alerts (everywhere only) adds, changes, orders and deletes those Pipelines with Tell and Wait steps, and changes, runs and deletes the alert jobs left from before 0.33.0; Create and change jobs does not cover them, and running Installations or assigning teams from a Pipeline stays with administrators. Only the Administrator profile has these two, so to let someone else see or make alerts, make a profile with them (Copy one) and grant it. Who receives alerts does not change: the certificate's owners and Settings, Notifications.

Team roles are built-in profiles for the team: viewer is Read-only, requester is Requester, operator is Certificate manager, lead is Team lead. Approvers limited to a team decide requests made for that team; nobody decides their own request.

Access can end on a date. Grants, team members, a Spotlight's Who can use it, Make their own automation and a template's named people take an end date; in a list, add until 2026-12-31 at the end of a line. It stops counting the moment it passes, and the server that runs jobs removes ended access within the hour, writing it to the Keeper's Log (grant-end, team-member-end, spotlight-access-end, template-person-end, automation-access-end).

Who makes automation

Automation (Installations and Pipelines), Discovery (a tab of Certificates) and ACME, SCEP and EST (tabs of Enroll) are made by administrators. Everyone else does not see Automation in the rail or those tabs, and the API answers 403 when they list or make Installations, ACME keys, SCEP challenges or EST credentials.

An administrator can let someone make their own, under Settings, People & Access, Make their own automation:

  • A person or group listed there sees Automation and may make Installations, ACME keys, SCEP challenges and EST credentials for any team they may enroll for (Installations: any team they operate), and personal ACME keys for themselves.
  • A team listed there lets its members do the same for that team only (and make personal ACME keys).

What they make still follows their profile and team role: a requester makes ACME keys, SCEP challenges and EST credentials for the team; an operator also adds the team's SSH Installations. They see what they and their team made, nothing else. Taking someone off the list hides Automation from them again; what they made keeps working, and administrators still see and change it.

Pipelines, Discovery (scans and CA reads), Drills and CA migration are always made by administrators, whoever is on the list: they reach every team's certificates. Pipelines is a tab of Automation, Discovery a tab of Certificates, and Replace many (drills and CA migration) sits under Settings, Bulk changes, shown to administrators only.

Teams

Teams are on the Teams page (called Certificate Owners in 0.11 and earlier). Someone who manages teams, or one of the team's leads, opens Members on one and lists its members, one per line, as an account or group and a role, with an optional end date:

EXAMPLE\web-admins = operator
EXAMPLE\alice = lead
EXAMPLE\carol = requester
[email protected] = viewer until 2026-12-31
Team role Can
viewer See the team's certificates, Installations and jobs, and alerts about them
requester Also enroll certificates for the team, through templates that allow the team, and take ownership of certificates for it
operator Also tag the team's certificates, add or remove the team (and only the teams they operate) as owner, add and change the team's SSH Installations (once they may make their own automation), run the team's jobs and destroy its kept keys
lead Also add and remove the team's members up to operator, change its email and weekly summary, and make and look after the team's API tokens

Only people who manage every team (Manage teams everywhere, or Settings, People) make leads, rename or delete a team, and change its auto-renew or change window; a lead does not change another lead. A team's API tokens, under Tokens on Teams, are members of the team as viewer, requester or operator, so a script does no more than the team; the team's email and whoever made a token are told 30 and 7 days before it expires.

People are not teams. Whoever enrolls a certificate owns it as a person as well as through the team they pick, and a personal ACME key's certificates are owned by its person. Lighthouse keeps that person as an owner named after their account (such as EXAMPLE\alice), made the first time it is needed. It shows among a certificate's owners and in My Spotlights, but is never listed as a team: not on Teams, in Reports' Over list, in team pickers, in weekly team summaries or in GET /owners and GET /teams. A new team cannot take its name. Upgrading marks the ones already there (an owner named after an account, with no team Spotlight and no other member). Administrators see everything through Every certificate, so they need no team of their own.

A team owns a certificate when it is set as an owner on it, or when one of the team's Spotlights matches it. A team Spotlight is a Spotlight with Owned by set to the team: server:web01.example.com or server:*.example.com for what is served there, folder:D:\Certs\Web for files under a folder, ca:Issuing CA for what a CA issued, or any other rule below, such as domain:shop.example.com. Team Spotlights are added on Teams (+ Spotlight) or from the Spotlight dialog, by administrators only (see Who can use a Spotlight). Locations and ownership rules from 0.11 and earlier became team Spotlights when the database was upgraded.

Once an administrator lets them make their own automation (see Who makes automation), team operators can add Installations for their team: SSH file installations only (IIS renewal carries the EJBCA client-certificate settings, so it stays with administrators), through a template open to the team, with a keystore password stored under a secret name starting installation-.

Team scoping always applies: signing in alone gives nothing, even without a viewers group (from 0.21.0; before, every signed-in Windows user saw everything when --viewers was not set). To let everyone in the domain see everything, set --viewers to a group such as CONTOSO\Domain Users. Certificates no team owns are seen only by administrators and --viewers. A certificate outside your teams answers 404, as if it did not exist. Creating and editing other jobs, templates and teams stays with administrators.

Whoever enrolls a certificate owns it personally: Lighthouse makes an owner named after their account, with them as its operator, and adds the team they enrolled for as a second owner.

Install

Step by step from an empty server, with the database and the service account, is in Installing and upgrading Lighthouse. This section describes every screen and setting.

For a new client, run Lighthouse-Setup-<version>-x64.exe (it adds the IIS prerequisites first, see Running in IIS) and follow the screens. Lighthouse-<version>-x64.msi shows the same screens without the prerequisites:

  1. Database: the SQL Server and database name. Test connection signs in as you and says whether the database will be created or upgraded.
  2. Dashboard and sign-in: from other computers (HTTPS with Windows sign-in, a port and the HTTPS certificate, picked from the computer's Personal store) or on this computer only; the administrators group and, optionally, the viewers group. The groups are looked up in the domain.
  3. How Lighthouse runs: as an IIS website or a Windows service, as the built-in account (simplest), a domain service account with no password to manage (a gMSA, recommended for production), checked to be usable on this computer, or a domain user account with a password (.\name for an account of this computer only), whose password setup checks by signing in as it (see The service account).
  4. Website (IIS only): the site name, the app pool name, the host name people type (it starts as the certificate's name; empty answers every name on the port) and the port (443 to start with).
  5. Prerequisites: one line per check, each OK, Will do (setup does it), Check (Lighthouse installs, but look at it) or Fix (Next stays off until it is fixed, then Check again). It covers IIS and its features, whether another site already has the name and port (or, for a service, whether the port is free), the certificate (private key, expiry, whether it has the host name), whether the host name's DNS record points at this server, the database test, whether you may add SQL Server logins, and the firewall rule and access setup will add.
  6. Summary: everything about to be installed. License file takes the license file iRonShore sent; setup checks it and stops with the reason if Lighthouse would not accept it. Leave it empty to start the 30-day trial and add the license later under Settings, License (see Licensing). Save answers writes an answer file (below).

The finish screen opens the dashboard. Upgrading with the screens skips them and keeps the settings.

What SQL Server needs. The Lighthouse server reaches SQL Server (TCP 1433, plus UDP 1434 for a named instance) and the connection is encrypted, so SQL Server's certificate must come from a CA the server trusts, or tick Trust the SQL Server's certificate. The person installing signs in with Windows and needs dbcreator to create the database (or db_owner on an empty database a DBA made first), and securityadmin to add the login for the Lighthouse account (or a DBA adds it first). The Lighthouse account gets only db_datareader and db_datawriter. No SQL password is stored.

Answer files. The summary's Save answers writes a small JSON file (lighthouse-answers.json in Documents unless you change the path) with every choice, and never a SQL password. ANSWERS=<file> starts the screens from it, or installs silently:

Lighthouse-Setup-0.37.0-x64.exe /quiet ANSWERS=client.json

A relative path starts in setup.exe's folder. A property on the command line wins over the file. The HTTPS certificate's thumbprint differs per server, so when it is not on the new server, setup takes the valid certificate (with its private key) for the host name in the file. Only the names setup knows are allowed, so a typo stops the install instead of being ignored.

Every setting is also an MSI property, so the same install works silently from Intune, SCCM or Group Policy:

msiexec /i Lighthouse-0.37.0-x64.msi /qn /l*v lighthouse-install.log ^
  URL=https://0.0.0.0:5443 SIGNIN=Windows HTTPS_CERT=<thumbprint> ^
  VIEWERS="CONTOSO\Lighthouse Viewers" OPERATORS="CONTOSO\Lighthouse Operators" ^
  SERVICEACCOUNT=CONTOSO\gmsa-lighthouse$ ^
  CONNECTION="Server=<sql-host>;Database=Lighthouse;Integrated Security=true;Encrypt=true"

A silent install with no properties gives a server on http://localhost:5080 without sign-in that uses SQL Server on the same machine. To try Lighthouse on a PC without SQL Server, use a SQLite file instead (one machine only, not for production):

msiexec /i Lighthouse-0.37.0-x64.msi PROVIDER=Sqlite CONNECTION="Data Source=C:\ProgramData\Lighthouse\data\lighthouse.db"
Property Default Meaning
URL http://localhost:5080 Where to listen (--url)
SIGNIN None None or Windows
SIGNIN_PRIMARY windows The main sign-in: windows, adfs, entra, okta or oidc (another OpenID Connect provider). A provider is added under that id with Windows sign-in kept as another way in (see Main sign-in). Remembered for upgrades
SIGNIN_AUTHORITY, SIGNIN_CLIENT_ID The main provider's address (issuer) and the client ID it gave Lighthouse
SIGNIN_SECRET The main provider's client secret. Stored like lh secret set signin-<id>, never in the settings file or an answer file
HTTPS_CERT Thumbprint of the HTTPS certificate in LocalMachine\My
VIEWERS, OPERATORS, KEY_RECOVERY Windows groups (see Roles)
KEY_ENCRYPTION_CERT Made by setup Thumbprint of the certificate kept keys are sealed to. Left empty, setup makes one (CN=Lighthouse key encryption, RSA 3072) or reuses the one it made before; with GROUP_CERT, it uses the group certificate when its key is RSA 3072 or more (see Keeping private keys)
KEY_ENCRYPTION_PROVIDER Microsoft's software provider The key storage provider setup makes the key-encryption key in when it makes the certificate, such as an HSM's ("SafeNet Key Storage Provider", "nCipher Security World Key Storage Provider"). The key is made there and cannot be taken out (see The key in an HSM). Ignored once the server has a key-encryption certificate
CLIENT_CERTS Thumbprints, comma-separated, of client certificates jobs sign in to CAs with (EJBCA). The service account gets read on their private keys
GROUP_CERT Thumbprint of the group certificate, the same on every server that shares the database (see Two or more servers). The service account gets read on its private key
RUN_JOBS 1 0 for a server that must never run jobs. Servers that share a database with 1 take turns (see Two or more servers)
PROVIDER, CONNECTION SqlServer, the Lighthouse database on localhost The database
SERVICEACCOUNT NT SERVICE\Lighthouse A gMSA (ending in $), a domain user account (CONTOSO\svc-lighthouse), a local account (SERVER\name) or the built-in virtual account. With the virtual account, SQL Server on another machine sees the server's computer account (CONTOSO\SERVER$), so give that account the database roles. See The service account
SERVICEPASSWORD The password of a domain user or local account in SERVICEACCOUNT, needed on every install, upgrade and repair (the screens ask for it). Setup signs in as the account to check it, then gives it to the service or app pool. Kept out of the MSI log, answer files and the settings file; ignored for a gMSA or the built-in account
HOSTING service service runs the Windows service. iis runs the IIS site Lighthouse instead (see Running in IIS)
SITE_NAME, APP_POOL Lighthouse The IIS site and app pool names (letters, digits, spaces, dots, dashes, underscores; up to 64). The built-in account is IIS APPPOOL\<APP_POOL>. Remembered for upgrades; a new name replaces the old site and pool
ANSWERS An answer file saved on the summary screen
LICENSE_FILE The license file iRonShore sent, as a full path. Setup checks its signature and copies it to %ProgramData%\Lighthouse\license.lhlicense; the server adds it to the database the first time it finds none there. Empty: the 30-day trial (see Licensing)
MIGRATE_DB 1 1 creates or upgrades the database schema as the person installing, who needs DDL rights. 0 skips it; run lh db migrate --settings "%ProgramData%\Lighthouse\lighthouse.json" yourself

The MSI:

  1. installs the self-contained program to C:\Program Files\Lighthouse (no .NET install needed);
  2. creates the Lighthouse service, started automatically and restarted a minute after a failure, running lh serve --settings "%ProgramData%\Lighthouse\lighthouse.json";
  3. writes the settings to %ProgramData%\Lighthouse\lighthouse.json, readable only by SYSTEM, administrators, the service account and the person installing;
  4. registers the Lighthouse event log source, gives the service account read access to the private keys of HTTPS_CERT, KEY_ENCRYPTION_CERT, GROUP_CERT and CLIENT_CERTS, and opens the port in Windows Firewall (rule Lighthouse) when the URL is not localhost;
  5. creates or migrates the database and gives the service account read and write on it (db_datareader, db_datawriter, creating its login when needed), then starts the service.

Setup writes what it did to %ProgramData%\Lighthouse\setup.log. If a setting would stop the server from starting (for example an https URL without HTTPS_CERT), the install stops and rolls back, and the log says why.

Changing settings later: run the MSI again with only the properties to change (an empty property keeps the current value, - clears an optional one), or edit lighthouse.json and restart the service.

Upgrading: run the new MSI with no properties. It keeps the settings, the service account and the database, and migrates the schema. On a large inventory the schema step can take a few minutes; lh db migrate says what it did. With a domain user or local account, Windows needs its password again: a double-click upgrade asks for it on one screen, and a silent one needs SERVICEPASSWORD. Back up the database first; the whole procedure, with two or more servers, Outposts and going back, is in Upgrading Lighthouse and Outposts.

Uninstalling removes the program, the service and the firewall rule. The settings file, secrets and the database are kept.

Running in IIS

With HOSTING=iis (or IIS website on the setup screens) Lighthouse runs as the IIS site Lighthouse in its own app pool instead of a Windows service. It needs:

  • the Web Server (IIS) role, with Windows Authentication when sign-in is Windows;
  • the ASP.NET Core Hosting Bundle for .NET 10, which installs the ASP.NET Core Module. The program itself stays self-contained.

setup.exe does the prerequisites for you. Lighthouse-Setup-<version>-x64.exe turns on the IIS features (Web Server, Windows Authentication, Application Initialization, the IIS console), installs Microsoft's Hosting Bundle (carried inside, so the server needs no internet), then runs the MSI with its screens. Anything already there is skipped, and the prerequisites stay when Lighthouse is removed. A first install from setup.exe offers the IIS website; an upgrade keeps how Lighthouse runs, and an existing service stays a service unless you give HOSTING=iis. It takes the MSI's properties on its command line: Lighthouse-Setup-0.16.0-x64.exe /quiet URL=https://lighthouse.example.test HTTPS_CERT=<thumbprint>. For Intune, SCCM or Group Policy, deploy the prerequisites your usual way and the plain MSI. setup.exe installs only the server; an Outpost installs from its own MSI (see Outposts).

Setup then creates the app pool Lighthouse (no managed code, always running, no idle time-out, no timed recycling, preloaded, because the scheduled jobs run inside it) running as SERVICEACCOUNT (a gMSA, a domain user account with its password, or the built-in IIS APPPOOL\Lighthouse), the site Lighthouse on the port in URL with C:\Program Files\Lighthouse as its folder (web.config runs lh serve --iis), Windows Authentication on the site, and the HTTPS certificate binding (netsh http add sslcert) for HTTPS_CERT. The site starts after the database is migrated.

With a host name in URL (for example URL=https://lighthouse.example.test, port 443) the site binds that name with SNI, so it shares 443 with a CA's web enrollment or any other site. With an address (https://0.0.0.0:5443) it takes the whole port. Setup stops before changing anything if another site already binds the same name and port. Application Initialization (Web Server, Application Development) is also needed, so Lighthouse and its jobs start with IIS rather than at the first visit. Switching back with HOSTING=service removes the site, app pool and binding and creates the service. Uninstalling removes them too.

Moving to another server: install the MSI on the new server with the same database and MIGRATE_DB=0, then uninstall it from the old one. Settings, jobs and the inventory are in the database and come along. Secrets do not: they are sealed with DPAPI to the old machine. Set them again on the new server, under Settings, Notifications (the SMTP password) and Settings, Integrations (webhook URLs, tokens and passwords) and with lh secret set for any a job names. Client certificates for CA sign-in are made on the new server; name them in CLIENT_CERTS and update the jobs' thumbprints.

Reinstalling on the same server (even with a new, empty database): keep %ProgramData%\Lighthouse\secrets. The SMTP password, Teams webhook and job secrets are sealed to this machine and come back with the folder; without it, set them again.

Replacing a manual install (a service created with New-Service): remove that service and its firewall rule first, then install the MSI with the same settings.

Stop-Service Lighthouse; sc.exe delete Lighthouse
Remove-NetFirewallRule -DisplayName "Lighthouse HTTPS"

Open https://<server>:5443/ from a PC. The signed-in user shows at the top right; click the name for their access, sign-in and teams.

Building the MSI

On Windows with the .NET 10 SDK (WiX comes from NuGet):

.\build\package-msi.ps1 -CertificateThumbprint <code-signing thumbprint>

It runs the tests (or only builds, with -SkipTests, for a commit whose full test run already passed), publishes lh for win-x64, signs lh.exe and the Lighthouse.*.dll files, builds artifacts\Lighthouse-<version>-x64.msi, signs it with an RFC 3161 timestamp, then builds and signs artifacts\Lighthouse-Setup-<version>-x64.exe (Burn's engine, then the bundle) and artifacts\Lighthouse-Outpost-<version>-x64.msi (the Outpost's own installer, not inside setup.exe) and checks every signature. setup.exe carries the Hosting Bundle from -HostingBundle <path>, or the latest .NET 10 one downloaded from Microsoft; either way its Microsoft signature is checked. Without -CertificateThumbprint nothing is signed, for testing only. The version is <Version> in Directory.Build.props.

A signed build also lays out artifacts\downloads\lighthouse\<version>\: setup.exe, the server MSI, the Outpost MSI and SHA256SUMS.txt (the sha256sum format). Upload that folder as it is to the website's downloads, as lighthouse/<version>/ (DEPLOY.md in the website's repository says how); the website answers each file at https://ironshoretech.com/downloads/lighthouse/<version>/<file>, the permanent address of each version's installers, behind the downloads sign-in. The Outposts page links the files of the server's own version there, so upload a version's folder when that version is published, and never change a folder once uploaded. The build also writes the documentation pages to artifacts\website\docs\lighthouse\ from these docs: with each published version, copy them over public/docs/lighthouse/ in the website's repository and open a pull request there; merging it publishes them at https://ironshoretech.com/docs/lighthouse/ (Cloudflare Pages builds the site from that repository). To write them on any computer with the .NET 10 SDK: dotnet run --project tools/Lighthouse.WebsiteDocs -c Release.

The service, or the IIS site, logs warnings and errors to the Windows Application event log under the source Lighthouse.

Sign-in from a browser

Sign-in is Windows Negotiate: Kerberos when the PC is domain-joined and the site is in the Local intranet zone, NTLM otherwise. A PC that is not in the domain gets a sign-in prompt; enter DOMAIN\user. For Kerberos with a domain service account, register the SPN HTTP/<server-fqdn> on that account. Negotiate needs HTTP/1.1, so the server turns HTTP/2 off when Windows sign-in is on.

In IIS with a gMSA or domain user app pool, Kerberos also needs IIS to decrypt tickets with the app pool account: register HTTP/<host name> on that account (setspn -S HTTP/lighthouse.example.test EXAMPLE\gmsa-lighthouse$) and set useAppPoolCredentials="true" on the site's Windows Authentication. Without the SPN, browsers fall back to NTLM, which still works.

Signing in from a browser on the Lighthouse server itself, by its host name, gets a 401: Windows' loopback check refuses NTLM to a name that points back at the same machine. Test from another PC.

Optional features

Some parts of Lighthouse are optional, so a server shows and answers only what its administrators chose. ACME is on on a new server (nothing enrolls through it until someone makes an ACME key); every other switch is off on a new server. An administrator turns them on or off on Settings, Server, Features or on a feature's own greyed-out page (administrators and people who look after the Server), or with PUT /api/v1/settings/features/<key> and {"enabled": true}. A change applies at once on every server sharing the database and is audited as feature-on or feature-off.

Some switches turn on several parts together. The parts keep their own keys, which routes, jobs and license files use, and PUT /api/v1/settings/features/<part> turns on the part's whole switch.

Feature Key Parts When on
ACME acme acme Enroll, ACME, and the ACME directories at /acme/<template id>/directory (see ACME clients). On on a new server
Device enrollment device-enrollment scep, est Enroll, SCEP and the SCEP addresses at /scep/<template id> (see SCEP devices); Enroll, EST and the EST addresses at /.well-known/est/<template id>/ (see EST clients)
Network appliances appliances appliances Installations on F5 BIG-IP, Palo Alto firewalls and Citrix ADCs (see Installing on network appliances)
Replace many replace-many drills, ca-migration Settings, Bulk changes, Replace many: from the same CA (a drill) and from another CA (a CA migration, Enterprise license) (see Replace many)
Move move move Settings, Move: in from Keyfactor Command or a move file, out to Keyfactor Command (see Moving in and out)
Macs from Jamf Pro macs macs The job kind Read the certificates on Macs from Jamf Pro (see Macs (Jamf Pro))
Cloud vaults cloud-vaults cloud-vaults The job kind Read the certificates in a cloud vault and the Installation kind Install and renew a certificate in a cloud vault, for Azure Key Vault, AWS Certificate Manager and Google Certificate Manager (see Cloud vaults)
AI ai mcp, ai-owner-suggestions, ai-explain The MCP address /mcp, for AI assistants with an API token (see AI assistants); the MCP tools list_unowned and suggest_owner, so an AI assistant suggests owners in Needs an owner, administrators' tokens only (see AI owner suggestions); Explain buttons on failed jobs, requests, log warnings and errors, Keeper's Log failures and certificates, and Settings, Integrations, AI model (see Explain with AI)
Outposts outposts outposts Settings, Outposts, and Run from on scans and Windows, Linux and appliance Installations: small Windows services, or pods in a Kubernetes cluster, that scan and install in networks this server cannot reach, and Kubernetes Installations (see Outposts and Kubernetes clusters)
Make templates on the CA ca-templates ejbca-profiles, adcs-templates New profile and Autoenrollment on an EJBCA CA, and New template on an ADCS CA, under Settings, CAs (see New EJBCA profiles, Autoenrollment from EJBCA and New ADCS templates)

No switch. Webhooks (webhooks), log forwarding (log-forwarding) and the more public CAs (public-cas) are always there, within the license, because nothing happens until an administrator adds a webhook, a log destination or a CA. Before 0.28.0 they had switches; a saved switch for them is now ignored.

While a feature is off, administrators see its tab greyed out and marked off, and opening it shows a Turn on button, so a feature is turned on where it is set up (outside the license, the page says which license has it instead). On Features, each feature that is on has Set it up, which opens the page where it is set up. Everyone else does not see the tab at all, and an old link to it just says the feature is off and to ask an administrator. Its API routes answer 404 with an error saying where to turn it on, as if they were not there. Turning a feature off removes nothing: ACME keys, SCEP challenges, EST credentials, drills, CA migrations, Outposts, cloud vault jobs and appliance Installations come back as they were when it is turned on again.

Upgrading. A switch saved before 0.28.0 under a part's key still counts: a switch with several parts is on if any of its parts was switched on, until an administrator saves the switch itself (which replaces the parts' old values). A server upgraded from 0.15.0 or earlier has no saved switches. Until an administrator saves one, a feature is on only if it was already in use: an ACME key, a SCEP challenge, an EST credential, a drill, a CA migration, a move, a Jamf Pro job, an appliance Installation or a cloud vault job exists (AI always starts off), and ACME is on. GET /api/v1/features says, per feature, whether it has been saved, and lists its parts, each with whether it is enabled and licensed.

Everything else, such as Change watch, auto-renew, the weak certificate check and the weekly team summary, is part of Lighthouse and has its own setting where it is used.

A feature also has to be in the server's license (see Licensing). One that is not shows its license tier on Settings, Features and cannot be turned on; its routes answer 404 saying which license has it.

Licensing

Lighthouse is licensed with a license file that iRonShore signs. Lighthouse checks the signature itself and never calls home, so a server with no internet access is licensed the same way. One license covers every server sharing the database.

  • Adding it: the License file box on the installer's Summary screen (or LICENSE_FILE=<file>), or Settings, License (administrators only), which shows who the license is for, its tier, how many certificates it covers and until when. A new license replaces the old one at once. GET /api/v1/license and PUT /api/v1/settings/license do the same from a script. Each license added is in the Keeper's Log as license-add.
  • No license yet: a 30-day trial with every feature and up to 500 certificates, starting the first time the server looks (after an upgrade, the day of the upgrade).
  • Tiers: what decides the optional features. Everything else (inventory, discovery, enrolling, renewals, Installations, Spotlights, teams, approvals, alerts, Pipelines and the API) is in every tier.
Tier Optional features
Standard ACME, Device enrollment (SCEP, EST), Network appliances, More public CAs, webhooks, log forwarding, Outposts
Professional Standard, plus Replace many (drills), Macs from Jamf Pro, Cloud vaults, AI (MCP, AI owner suggestions, Explain with AI)
Enterprise Professional, plus Replace many's CA migrations, Move, and Make templates on the CA (EJBCA profiles, ADCS templates)

A license can also add a single feature on top of its tier. ACME, SCEP, EST, Network appliances, More public CAs and Outposts are in every tier because certificates renew through them, and a license must never stop a renewal.

  • Certificates counts what Lighthouse manages now: certificates that are not expired and not revoked, from any source.
  • When it ends: administrators see a banner 30 days before. After the end date there are 30 days of grace. After that new certificates stop (the Enroll page and POST /enroll say why), but renewals, inventory, discovery, alerts, ACME, SCEP and EST keep working, so a lapsed license never causes an outage. An ended license keeps its tier's features; an ended trial keeps Standard's.
  • Over the certificate count: a banner for administrators, and 30 days to get back under it or add a larger license before new certificates stop. Renewals are never stopped.

How iRonShore makes license files is in docs/licensing.md.

An administrator can show a line of text across the top of every console page and the API Explorer, for everyone signed in: for example TEST SERVER, a classification marking such as UNCLASSIFIED, or a short announcement. Set it under Settings, Application, Banner, or with PUT /api/v1/settings/banner:

{ "enabled": true, "text": "TEST SERVER: changes here do not reach production", "color": "amber", "bottom": false }
  • Text: one line of plain text, at most 200 characters, never read as HTML. A long line is cut short with an ellipsis; hovering shows it whole.
  • Colour: blue, green, amber, red, purple or grey, or a custom background as #rrggbb, with black or white text, whichever reads better.
  • Bottom: also shows it at the bottom of every page, as classification markings usually are.

The banner stays in place while the page scrolls, and drawers and the signed-in panel open below it, so it is never covered. Turning it off keeps the text and colour for later. Saving is audited as a settings change. People see a change on their next page load. GET /api/v1/banner (anyone signed in) returns it. It is not shown on the sign-in pages of Windows or your sign-in provider, which Lighthouse does not draw.

Message for everyone signed in

For anything longer than the banner, such as planned maintenance, an administrator can post a message under Settings, Application, Message for everyone signed in, or with PUT /api/v1/settings/notice:

{
  "enabled": true,
  "title": "Planned maintenance on Saturday",
  "text": "Lighthouse is down on Saturday 25 October from 18:00 to 20:00 UTC.\n\nRenewals due that evening run afterwards. Questions: https://intranet.example.com/pki",
  "kind": "warning",
  "from": "2026-10-23T08:00:00Z",
  "until": "2026-10-25T20:00:00Z",
  "dismissible": true
}
  • It shows at the top of every console page, under the header, for everyone signed in, in blue (info), amber (warning) or red (critical).
  • Title is optional, one line, up to 120 characters. Text is up to 4000 characters of plain text: a blank line starts a new paragraph, line breaks are kept, and http and https addresses become links that open in a new tab. Nothing else is read as markup.
  • Show from and Show until are optional. The console takes them in your own time and the server keeps them in UTC. Outside the window the message is saved but not shown, so you can post next week's maintenance notice today.
  • People may close it (on by default) gives the message a Close button. Closing hides it for that person only, until an administrator saves the message again; each save is a new version. Turn it off for a message everyone must keep seeing.

Saving is audited as a settings change; closing it is not. GET /api/v1/notice returns the message as the caller sees it, with showing (shown to them now) and dismissed (they closed it). PUT /api/v1/me/notice with {"dismissed": true} closes it for the caller.

Sign-in with other providers (OpenID Connect)

Besides Windows sign-in, Lighthouse can sign people in through any OpenID Connect provider: Microsoft Entra ID, Okta, Google, Keycloak, AD FS and others. Set signIn in %ProgramData%\Lighthouse\lighthouse.json to:

  • OpenId: only the providers below. IIS does no Windows Authentication for the site.
  • WindowsAndOpenId: Windows sign-in and the providers, one of them the main sign-in.

Settings, Sign-in

Administrators set up sign-in in the console, under Settings, Sign-in, without editing files or restarting:

  • Add sign-in provider offers AD FS, Microsoft Entra ID, Okta, Google, Keycloak or any OpenID Connect provider, and shows the redirect address to register with it. Enter its address, client ID and client secret, and map its groups to Lighthouse groups. The secret goes to the secret store and is never shown again.
  • Save and test sign-in opens the provider in a new tab and shows the name and groups it sent. It signs nobody in and leaves your own session as it is. A provider becomes the main sign-in only once it is on and has passed a test since its address, client ID, resource or scopes last changed.
  • A provider can be turned off (kept, and still testable, but not a way in) or removed. The page refuses to turn off or remove the provider you signed in with, or the main sign-in.
  • Every change is in the audit log, without the secret.

What the page saves is kept in the database (setting signin), so every server sharing it uses the same methods; the others pick up a change within 30 seconds. Once the page has saved, its list replaces signInProviders, primarySignIn and showOtherSignIns in lighthouse.json, which only set the starting point (and what a silent install with SIGNIN_PRIMARY writes). Windows sign-in itself stays a setup choice. A server that runs Windows sign-in alone needs a restart before the first provider works, and the page says so.

Main sign-in

People are not asked how to sign in. Opening Lighthouse goes straight to the main sign-in, primarySignIn in the file (windows or a provider's id), which setup's main sign-in screen or SIGNIN_PRIMARY sets. Without it, Windows is the main sign-in when it is on, otherwise the first provider. Someone who signed in another way before goes back that way instead: the browser remembers the last way in (see Signing back in after a timeout).

The other methods are secondary, for uses such as an administrator's way in while the provider is down, or a second provider for partners. They are offered under Other ways to sign in on the sign-in page, which people see after signing out or when sign-in fails, and always at https://<lighthouse host>/signin?other=true. Set "showOtherSignIns": false to leave them off the page; their address still works. Settings > Application shows the main sign-in and the others.

"signIn": "WindowsAndOpenId",
"primarySignIn": "adfs",
"showOtherSignIns": true,

Then list the providers and restart the service or IIS site:

"signIn": "WindowsAndOpenId",
"signInProviders": [
  {
    "id": "entra",
    "name": "Microsoft Entra ID",
    "authority": "https://login.microsoftonline.com/<tenant id>/v2.0",
    "clientId": "<application (client) id>",
    "groups": { "<object id of an Entra group>": "EXAMPLE\\Lighthouse Operators" },
    "everyone": null
  }
]
Setting What it does
id Lower-case letters, digits and dashes. Upper-cased, it is the prefix of every user and group from this provider: ENTRA\[email protected], ENTRA\Web Devs.
name The text of the sign-in button.
authority The issuer URL; Lighthouse reads <authority>/.well-known/openid-configuration. Use a tenant-specific URL for Entra ID, not common.
clientId The client ID the provider gave Lighthouse.
groups Outside groups, as the provider sends them, mapped to Lighthouse group names, so they count wherever that name is used: the viewers, operators or key recovery group, or a team member.
everyone A Lighthouse group every user of this provider is in. Leave it out unless every account the provider can sign in should get in.
userClaims Claims tried for the user's name. Default ["preferred_username", "email", "upn"], then sub.
groupClaims Claims that carry groups or roles. Default ["groups", "roles", "group", "role"] (AD FS sends group and role).
windowsNames true for AD FS on the same Active Directory: a name or group it sends as DOMAIN\name is used as that Windows name, without the provider prefix, and the user's name comes from winaccountname first, so Windows grants and teams apply. Leave it off for any other provider.
scopes Scopes beyond openid profile email, if the provider needs one to send groups (only the Okta org authorization server needs groups; its default authorization server refuses it).

Step-by-step setup for AD FS, Entra ID, Okta and a local Keycloak is in Sign-in providers.

Register Lighthouse with the provider as a web application using the authorization code flow, with the redirect URI https://<lighthouse host>/signin-oidc/<id>, for example https://lighthouse.example.test/signin-oidc/entra. Application in the console lists each provider's redirect URI. If the provider gave a client secret, store it on the server, in an elevated prompt:

lh secret set signin-entra

Without a secret, Lighthouse signs in as a public client with PKCE only (Okta and Keycloak allow this; Entra ID web apps need a secret).

Who gets in. Nobody is a viewer just because they signed in, even without a viewers group: a provider such as Google would let any account in, and a domain is every employee. They need a mapped group, the provider's everyone group, or a team that lists them or one of their groups. A team member can be a provider user (ENTRA\[email protected]) or a provider group (OKTA\Web Devs), written the same way as Windows names.

Groups. Entra ID sends group object IDs in groups when the app registration's token configuration adds the groups claim; with more than 200 groups it sends none, so assign app roles instead and map them (they arrive in roles). Okta sends group names when a groups claim is added to the authorization server (see sign-in-providers.md, which also covers the access policy Okta needs). AD FS sends DOMAIN\group names in group when an issuance rule sends Token-Groups (qualified by domain name) and the client asks for allatclaims. Google sends no groups, so use everyone together with teams.

Sessions. Sign-in sets a cookie. It ends after 30 minutes without use and after 12 hours however much it is used; administrators and people who look after Settings, People change both on Settings, Sign-in, Sessions (5 to 480 minutes, 1 to 24 hours). Typing, clicking and scrolling in the console count as use; pages that refresh themselves, such as Server Log's live lines, do not. Two minutes before the end the console shows a countdown with Stay signed in; every open tab of the browser shares it, so working in one keeps the others open, and a timeout in one clears and signs out them all. When time runs out the screen is cleared at once and the sign-in page says why, and the Keeper's Log has a sign-out "after 30 minutes without use". The server keeps its own clock too, a minute behind the console's, so a closed or sleeping laptop is signed out as well. Signing in again goes the way used last; with AD FS or Okta whose own session is still alive that happens without a password, unless After a timeout or Sign out, ask for the password again is on (it sends prompt=login; Windows sign-in cannot be made to ask). API tokens, ACME, SCEP, EST and Outposts have no session and are not affected. With Windows sign-in alone there is no session: after the minutes without use the console clears its screen, and opening it again signs back in with Windows. Groups are read once, at sign-in, so a change in group membership shows after the next sign-in. The cookie keys are kept in %ProgramData%\Lighthouse\sign-in-keys (DPAPI, machine scope), so a restart keeps people signed in. To sign out, click your name top right, then Sign out at the bottom of the panel.

IIS. With WindowsAndOpenId, the site keeps Windows Authentication on and anonymous access on, and only Sign in with Windows asks the browser for Windows credentials. With OpenId, setup does not turn Windows Authentication on. The setup screens set up one provider as the main sign-in (AD FS, Entra ID, Okta or another OpenID Connect provider), with Windows beside it; more providers, group mappings and everyone are added in the file. An upgrade keeps OpenId or WindowsAndOpenId when providers are listed, and keeps the main sign-in. To go back to Windows sign-in only, change signIn in the file.

API

Scripts use the same API as the console under /api/v1, with an API token made on People & Access. The reference is at /api/v1/docs; see The API. In IIS, keep Anonymous Authentication on beside Windows Authentication (the default), so requests with a token reach Lighthouse.

AI assistants

An AI assistant the customer chooses, such as Claude, Copilot or Cursor, can answer questions about the inventory ("what expires this month with no owner?", "which certificates does the Payments Spotlight hold?") by reading Lighthouse over the Model Context Protocol at https://<server>/mcp. Lighthouse has no AI of its own and, for this, sends nothing to any AI service; the assistant asks Lighthouse, and the customer decides where the assistant's model runs.

  1. An administrator turns on AI under Settings, Features.
  2. Make an API token on People & Access with the built-in profile Read-only, for all teams or only some. The assistant sees exactly what that token may see.
  3. Add https://<server>/mcp to the assistant as a streamable HTTP MCP server with the header Authorization: Bearer <token>.

Every tool only reads (inventory summary, finding certificates by Spotlight rule, one certificate, Spotlights, teams, CAs, jobs, recent alerts); see The API for the list. A browser session cannot use /mcp, only a token. In IIS it is reached like the API, with Anonymous Authentication on beside Windows Authentication. Turning the feature off or removing the token stops the assistant at once.

AI owner suggestions

With AI on (the part ai-owner-suggestions), /mcp also lists two more tools:

  • list_unowned gives the unowned certificates, unowned longest first, to an administrator's token only (anyone else's gets an error): their names, where they were found, who asked the CA, the CA template, days unowned, Lighthouse's own suggestions (what each rests on) and the teams that said not ours, plus every team with up to ten names it owns, to compare with.
  • suggest_owner leaves a suggestion (certificate, team id, a one-line reason of at most 300 characters). Administrators' tokens only. Nothing changes hands: it shows in Needs an owner marked AI until an administrator gives the certificate to that team or says not ours. A newer suggestion for the same team replaces the older one; a team that said not ours cannot be suggested again. Audited as owner-suggest-ai, by the token's name.

The token needs Claim certificates for the team it suggests (the Requester profile for that team), or Assign owners everywhere. Names are masked by default: host names, folders and accounts reach the assistant as stand-ins such as h3f9a1c.h77be02.com, keyed by a random key made on this server, so the same name always gets the same stand-in (the last label of a host name, such as com, is kept) and suggestion reasons are left out. An administrator can send names as they are with the button on the feature's row under Settings, Features, or PUT /api/v1/settings/ai-suggestions with {"maskNames": false}. Lighthouse still sends nothing to any AI service itself.

Explain with AI

An Explain button on a failed job or Installation, a failed or rejected request, a server log warning or error, a failure in the Keeper's Log (such as alert-failed, for administrators) and on a certificate asks an AI model what probably went wrong and what to check next. The answer comes back in plain words under four headings: what happened, the likely cause, what to check, what to do next. The AI only suggests: it cannot change anything, and a person decides what to do.

Lighthouse has no AI of its own. Explain uses the customer's own model:

  1. An administrator turns on AI under Settings, Features. It is off on new and upgraded servers.
  2. Under Settings, Integrations, AI model, pick the kind and fill in the address and model:
    • OpenAI-compatible: a model on your own network, such as Ollama (http://localhost:11434/v1, model llama3.1, no key), LM Studio or vLLM, or OpenAI itself (https://api.openai.com/v1 with a key).
    • Azure OpenAI: https://<resource>.openai.azure.com/openai/v1, the deployment name as the model, and the resource's key.
    • Anthropic (Claude): https://api.anthropic.com, a model such as claude-opus-5-5, and an API key from your own account.
  3. Send a test question checks the address, model and key; it sends nothing from Lighthouse.

What is sent is only what Lighthouse knows about the one item: for a job its kind, schedule, status, last result and settings, and with See the server log the server log lines about it; for a request its status, outcome, Pipeline and template; for a certificate the same details the AI assistants get; for a log line the line and the lines before it; for an audit entry what happened and when. Before anything leaves, Lighthouse takes out private keys, passwords, tokens and anything named like a secret (the same cleaning as the server log), and leaves out the PEM, owners' email addresses and who asked or did something. With Hide host names on (the default), host names, IP addresses and email addresses are replaced with stand-ins such as host-1.example and 192.0.2.1, and put back in the answer, so the model never sees them. Short server names without a dot (CA01) and CA names are not hidden.

The dialog shows exactly what was sent under What Lighthouse sent. Each question is in the Keeper's Log as ai-explain, with the item, the model and its host, how much was sent and how many names were hidden. The API key is kept in the secret store (ai-model-key), never in the database. An http address is accepted only for a model on the server itself (localhost, 127.0.0.1); anything else needs https, so the question and the key do not cross the network in the clear. A model saved with a remote http address before 0.21.0 is refused when asked, with a message to change it. Anyone who can see an item may explain it; Explain never shows them more than they could already see.

Jobs

Operators add jobs on the dashboard:

Kind Parameters Notes
Scan addresses, ports (numbers or web, mail, directory, windows, infra, common), optional SNI, Change watch (on by default) Same limits as lh scan: 65,536 addresses and 1,024 ports; see Change watch
ADCS sync CA config (HOST\CA Name), optional display name Windows only; the service account needs Read on the CA
EJBCA sync https URL, client certificate thumbprint, optional CA name, display name and TLS root PEM path The certificate sits in LocalMachine\My or the service account's store; give its EJBCA role read-only access
DigiCert sync API key secret name (default digicert-api-key), optional display name and API URL Reads every issued certificate in the CertCentral account; see Public CAs
Sectigo sync SCM customer URI, API login, password secret name (default sectigo-password), optional display name and SCM address Needs More public CAs on; reads every issued SSL certificate in the SCM account; see More public CAs
GoDaddy sync customer id (GUID), API key and secret secret name (default godaddy-api-key), optional display name and API address Needs More public CAs on; reads every issued certificate in the GoDaddy account; see More public CAs
Vault sync https address, PKI mount (default pki), token or AppRole (role id, mount), secret name (default vault-credential), optional namespace, display name and TLS root PEM path Reads every certificate the engine keeps; see HashiCorp Vault PKI
Macs from Jamf Pro https Jamf Pro address, API client id, client secret secret name (default jamf-client-secret) Read-only; see Macs (Jamf Pro)
Cloud vault Azure Key Vault (vault address, tenant and client id), AWS Certificate Manager (region, access key id) or Google Certificate Manager (project, location); sign in with a stored credential or as this server Read-only; see Cloud vaults
Expiry alerts (from before 0.33.0; none are added now) thresholds (default 60,30,14,7,0), SMTP relay, sender, default recipient, optional Teams webhook secret, dashboard link Moved to Pipelines; see Alerts
Weak certificate check (from before 0.33.0) optional default recipient, also check certificates only seen at a CA Moved to Pipelines; see Alerts
IIS renewal Windows server, IIS site, port, optional host header, EJBCA URL, client certificate, CA and profiles (or instead an enrollment template to issue through, on any CA), key type, renew-before days, maximum validity See Renewing IIS certificates
Replace-All Drill one CA name, one Spotlight or both, stop after failures in a row (default 3) Run now only; started and run from Settings, Bulk changes, Replace many. See Replace-All Drills
CA migration the old CA's name, the new template, optional Spotlight, stop after failures in a row (default 3) Run now only; started and run from Settings, Bulk changes, Replace many. See CA migration
Cloud vault certificate the vault as for Cloud vault, the certificate's name (an ACM ARN, or empty to import a new one), enrollment template, key type, renew-before days, optional subject, where it is served, and for Key Vault whether the key may be exported See Cloud vaults
Windows service Windows server, use (Remote Desktop, SQL Server, Exchange, AD FS, LDAPS, WinRM https or the machine store) and what it asks for (SQL instance, Exchange services, friendly name and service), where it is served, enrollment template, key type, renew-before days See Windows services and files
Files on Windows Windows server, PFX path and password secret or PEM paths, optional service, where it is served, enrollment template, key type, renew-before days See Windows services and files
Linux or Unix files server, SSH account and port, PEM paths or keystore file, alias and password secret, optional group, service, where it is served, enrollment template, key type, renew-before days See Installing certificate files over SSH

Schedules are daily HH:mm (UTC), every Nh or every Nm (at least 5 minutes), or empty for Run now only. Jobs run one at a time. A job that fails keeps its schedule and shows the reason. A sync that is refused (HTTP 401/403 or access denied) stops at once and is not retried until its next run.

Job parameters never hold secrets: certificates are named by thumbprint and stay in the Windows store.

Only one server should run jobs against a database. A second server on the same database (for example a test copy) should use --no-jobs.

The console

The left rail has seven places, and five more under Settings. A place with several pages shows them as tabs at the top:

  • Dashboard.
  • Certificates, with the Spotlights you pinned listed under it (up to eight; until you pin any, the few that need attention most). Each shows how many certificates it holds, red when one is expired or due within 7 days, amber within 30 and blue within 90; hover for the counts. Administrators also have the Discovery tab: scans, CA syncs and reads of Jamf Pro and cloud vaults, the jobs that bring certificates in.
  • Enroll (for people who may enroll): ask for a new certificate. See Enroll. Its ACME (keys for ACME clients), SCEP (challenge passwords for devices) and EST (credentials for EST clients) tabs are the ways machines enroll; made by administrators, shown to others only when an administrator lets them make their own (People & Access, Make their own automation).
  • Requests: requests waiting for approval and your own open requests. It opens on Waiting; approved, rejected, cancelled and failed requests are under Finished, and All shows both.
  • Automation: what Lighthouse does to certificates by itself. Installations (with their Recipes), made by administrators and shown to others only when an administrator lets them make their own, and Pipelines (for administrators).
  • Alerts are under Automation, Pipelines since 0.33.0 (Certificate expiring and Certificate weak), with Recently told; for administrators, and people an administrator gave See alerts or Create and change alerts, who see those two kinds of Pipeline only. The weekly team summary is set under Settings, Notifications.
  • Reports (for administrators, and people an administrator gave See reports or Create and change reports): Expiring soon, Executive summary, Ownership and Crypto health, with CSV, print to PDF, saved reports and scheduled emails. See Reports.
  • Settings: CAs, Templates & Policies, People & Access, Sign-in, Notifications, Integrations, Key Storage, Application, Cleanup, Outposts, Features, License, Move, Server Log and Keeper's Log. Under it in the rail:
    • Teams: who owns what. People who are not administrators have no Settings, so for them Teams is a place of its own in the rail, where it was before.
    • Replace many (in Bulk changes, for administrators, once Replace many is turned on): drills from the same CA and migrations to another CA on one page.
    • Server Log and Keeper's Log: what the server did and what went wrong, and who signed in and who changed what, for administrators and people who read the logs; auditors read the Keeper's Log too (see Server Log and Keeper's Log below).

Certificates (your pinned Spotlights) and Settings have places under them, and an arrow beside each folds those away. Lighthouse keeps which ones you folded for you on the server, so they stay folded on any browser you sign in from; when the page you are on is folded away, its heading stays lit. On a phone the rail is one row and nothing folds. Scripts can read the folded headings as railCollapsed on GET /api/v1/me and set them with PUT /api/v1/me/rail.

Old links such as #jobs, #acme, #pipelines, #cas or #access still open their page, now as a tab of the place it moved to; #locations opens Teams and #alerts opens Automation, Pipelines. #enroll/<template id> opens Enroll with that template picked, and #reports/<id> opens a saved report.

Fields with a grey example (an address, a path, a host name, a mailbox, a CN= name) fill in that example when you click or tab into an empty one, with the part most people change selected: the host in an address, the domain in a mailbox, the name after CN=, or a whole host name. Type over it, or press an arrow key and change only the part that differs, such as https://keyfactor.example.com/KeyfactorAPI becoming https://kf.yourcompany.net/KeyfactorAPI. Leave the field without changing anything and it goes back to empty, so tabbing through a form never fills in examples (an empty optional field still means the default). If a value you save still holds example, Lighthouse asks before saving. Plain words, numbers, secrets and fields with their own list of choices keep a normal placeholder.

At the top right of every page are Help (topics and how-to guides, with Search Help above the list of topics; press / there to jump to it), API (the API Explorer at /api/v1/docs) and your name. Help and API each open in a tab of their own, so the page you were working on stays as you left it; the new tab is signed in already, and a second click, or any Help link on a page, turns the same tab rather than opening another. Working in either tab keeps the console signed in, and a sign-out in one ends them all. Clicking your name slides in a panel with who you are signed in as (account, access, how you signed in, teams) and a Sign out button at the bottom. Signing out takes two clicks, your name and then Sign out, so a stray click in the header never signs you out. The version (Lighthouse 0.x.y) and copyright sit at the bottom of the left rail, or at the bottom of the page on a phone. The copyright runs from the first release to the server's current year, so it says © 2026 iRonShore Technologies, LLC in 2026 and © 2026–2027 iRonShore Technologies, LLC in 2027, without anyone changing it.

Certificate history

Open a certificate and its panel ends with History: its life from issue to expiry, newest first. It shows when it was issued (and who enrolled it, from which template, when that went through Lighthouse), when Lighthouse first found it and where (a server, a file, a Mac or a CA), where it was installed and taken out, owner, tag and Fact changes, kept keys, revoke requests and their decisions, when it was revoked and why, and when it expired, or, while that is still ahead, when it expires.

Renewals link the old and new versions both ways (Earlier version, Newer version, Latest version), so the whole chain can be walked. A renewal Lighthouse made is linked from its Keeper's Log entry; otherwise a newer certificate that is not revoked and covers all of the old one's names is its next version, the same rule expiry alerts use. People see the history of every certificate they may see, with who did each step, and links only to versions they may also see.

The history is put together from the inventory and the Keeper's Log when it is opened. Lighthouse keeps a note of which certificates each Keeper's Log entry names, so opening a history reads only that certificate's entries, however long the log. The upgrade that added the note goes through the entries already there: about 3.5 minutes for 3.2 million entries in the load test, and a moment on a typical log. A revocation made at the CA outside Lighthouse does not show yet, since CA syncs do not read revocations. Scripts use GET /api/v1/certificates/{thumbprint}/history. With Settings, Cleanup on, places the certificate is no longer found at show as No longer found; once the cleanup removes the certificate itself its panel is gone, and what it was stays in the Keeper's Log (see Cleanup).

Site name

An administrator can give this installation its own name under Settings, Application, Site name, such as "Acme PKI". It is shown in place of "Lighthouse":

  • at the top of the left rail and in the browser tab (Certificates · Acme PKI);
  • on the sign-in, signed-out and test sign-in pages;
  • in alert email from Settings, Notifications: as the sender name when Notifications leaves From name empty, at the start of the subject (Acme PKI: 3 certificate(s) expiring within 30 days, Acme PKI weekly summary for Web) and in the closing "Sent by" line.

The version and copyright at the bottom left still say iRonShore Lighthouse, as does the small line under the sign-in box, and Help, the API Explorer and the API keep the product name, since they describe the product. Teams, Slack, Jira and ServiceNow messages, webhooks and log forwarding are unchanged, as are alert jobs that set their own SMTP server. Clear the field and save to go back to Lighthouse. The name is up to 40 characters, on one line, without <, > or quotes. It is kept in the database, so every server sharing it shows the same name.

This is the name people see, not the web address. The address (such as https://lighthouse.example.test) is set by setup's Website step; changing it also needs a DNS record and an HTTPS certificate for the new name.

Viewers see the Dashboard, Certificates, Templates and Teams. Team members see the same pages filtered to their teams, and Enroll when they are a requester. Automation is for administrators and for the people, groups and teams they let make their own (see Who makes automation); Pipelines are for administrators, each group under Settings for administrators and whoever has its permission, and the Keeper's Log also for auditors.

Enroll

Enroll is its own place in the rail, not something done from inside a template. It has two steps:

  1. Pick a template. The page lists, as cards, only the enabled templates the person may enroll through: every one for an administrator, otherwise those opened to them under Who may enroll (see Who sees which templates). Each card shows the CA, the key types, the longest life, the domains, whether it needs approval and which facts it asks for. With more than six, a search box filters them. With exactly one, it is picked already.
  2. Request. For team, then Make the key here (PFX) (with optional subject fields, see Certificate subject) or I have a CSR, the facts the template asks for (its own and those asked on every template), and Enroll (or Send for approval). The request is checked against the template before it goes to the CA, as before.

Templates are made and changed by administrators under Settings, Templates & Policies, which no longer has an Enroll button on each row. The API is unchanged: POST /api/v1/enroll with a templateId, and GET /api/v1/templates lists the templates the caller may use.

Dashboard is the first page. Your work at the top says what needs you: certificates due within 30 days, certificates that expired and were not revoked, requests waiting for your approval, your own open requests, and, for administrators, how many certificates need an owner, which opens the Needs an owner Spotlight (left out when Settings, Application says Admins are their intended owner). Below it, the active certificates you can see at a glance (a revoked certificate is in no count here): tiles for Active, Needs me (active and due within 30 days), Expired (past the end date and not revoked), Within 90 days, Needs an owner (Owned by Admins when Admins are their intended owner; and Keys kept when Lighthouse holds any), where certificates were found, the Runway (one large count per window: Expired, This week, This month, This quarter and Later, above a horizon line from 30 days ago to a year out), Weak certificates (weak keys, SHA-1 or MD5, long lifetimes; see Weak certificates), and bars for each spotlight, issuer and key type. Clicking a tile, a Runway or Weak certificates count, or a bar, opens Certificates with that rule lit as a spotlight.

Customize on the Dashboard makes it each person's own. Everyone can tick panels on or off, drag them (or use the arrows) into order and make any panel half or full width; on a phone every panel is full width. Tiles are the counts at the top: the built-in ones and any Spotlight the person can open (My Spotlights, their teams' Spotlights, ones shared with them; Needs an owner and Self-signed for administrators only), up to 12, each opening its Spotlight on Certificates. The Spotlights panel shows the biggest 8 or the Spotlights the person ticks. Three panels are about the person's own work: Due next (the ten certificates they can see that expire soonest, with Renew…, which follows the usual Renew and approval rules), Waiting on me (requests they may decide, only for people who approve) and My requests (their open requests and who each waits for). Saving changes only what that person sees; Reset goes back to their starting layout.

Which layout a person sees: their own if they saved one, otherwise their team's if the team's lead made one, otherwise the layout for everyone, otherwise the built-in one; Reset drops to the next one down. Someone in several teams with a layout picks one under Start from. People who can change Settings, Server pick Layout for: Everyone (the default); a team's lead picks Layout for: My team: , which the other members see but cannot change, and administrators do not look after team layouts. Either may Pin a panel there so people can move it but not hide it. When a starting layout changes after someone saved their own, their Dashboard says so once, with Look and Keep mine. The server decides what applies to each person: Customize offers only panels that apply to them (Suggested next steps for administrators, Waiting on me for approvers), and a tile for a Spotlight they can no longer open drops off their layout with a one-time note. A layout saved before 0.35.0 gains the new panels at the end, switched off. Hiding a panel only hides it: Suggested next steps still shows only to administrators and only until every step is done; each step drops off it once done, and Help keeps the whole list.

Certificates is the full list, with spotlights, search and two views. Table sorts and lets operators select many to tag or give an owner; Domains is a tree by DNS name. Export CSV downloads what you can see with the Status picked, with a Status column.

Status beside the search box picks Active (in date and not revoked; the default), Expired (past the end date and not revoked), Revoked or All. Revoked wins, so a revoked certificate never shows as expired; in the table it shows Revoked, struck through, where its days left would be. A Spotlight or search that names a status itself (state:expired, state:revoked, also with not) shows that status whatever the filter says, and the filter greys out; the built-in Expired Spotlight is state:expired. When the Status hides some certificates that match, the line under the title says how many, with a link that switches to All. Reports and certificate history always include every status.

The server does the searching, sorting and counting, so the browser holds only what is on screen however big the inventory is. Table shows 100 certificates at a time with Previous and Next below it; sorting a column sorts every match, not just the page. Ticking the box at the top of the table selects the page, and Select all in the bar then selects every match on every page (up to 10,000). Domains shows the 2,000 that expire first; search or pick a Spotlight to see others. The Dashboard's counts, tiles and bars, a Spotlight's preview and the rule picker's suggestions are worked out on the server too. The horizon under the Runway shows the 300 that expire soonest; the lanes above it count them all.

Click any certificate to open its details. From there you can download the PEM, set tags, facts and owners, renew with the same names, set up an IIS installation, or, for administrators, Give an owner when it has no owner.

Owner, just under the details, says who owns the certificate and why, for everyone who can see it: each owner is marked given (given by hand: by an administrator, Needs an owner, a Pipeline or when it was enrolled; it stays until someone changes it) or through a named team Spotlight (automatic, while the Spotlight's rule matches). Both count the same for who sees it, who is told and who may act on it. Administrators get Change owner… on every certificate that has an owner: pick the new team or person, take the owners given by hand off or keep them, and add why if you like; the history and the Keeper's Log record the new owners by name and the reason. Change owner never changes a Spotlight: when a team Spotlight also matches, the dialog says that team keeps owning it and links to the Spotlight, whose rule is what to change. A team's operators can still add or take off their own teams. A certificate with neither says Admins, by default and links to Needs an owner; the API gives defaultOwner: "Admins" for it (null when someone owns it).

Spotlights are saved searches. Only administrators (the Administrator profile everywhere) make, change and delete them; everyone else uses the ones they are given. They have their own page, are shown as chips above the list on Certificates (only the pinned ones once you have more than twelve, with a link to the rest), and the biggest eight are bars on the Dashboard. Each one lights up the certificates its rule matches and stays current by itself.

The Spotlight dialog builds the rule for you: pick a field (Subject, DNS name, Subject alternative name, Issuer, CA it came from, Template, Key type, Key size, Days until it expires, Weak, Server, Folder, Mac, Found by, Team or owner, Tag, Private key, each Fact, or Any text), how to compare it, and a value. Values are suggested from the inventory (fixed values, such as a fact's choices, are a list), and every condition must hold. The picker writes the rule text underneath, and typing there updates the conditions; a term the picker cannot show stays as text. GET /api/spotlights/fields returns the same fields for scripts.

Who can use a Spotlight

At the bottom of the Spotlight dialog, Who can use it lists the people, groups and teams an administrator gave it to, each with an access profile. Each of them sees the certificates the Spotlight matches and may do with them what the profile's certificate permissions allow: See certificates always, and Tag and assign owners, Ask to revoke, Revoke, Destroy kept keys and Recover kept keys when the profile has them. Read-only gives sight only; Certificate manager also tags, assigns and asks to revoke. Enrolling, claiming, approving, jobs and Installations are not given this way; they come from grants and team roles. A person or group is written as for a grant (EXAMPLE ame, [email protected], group:<name or id>, user:<id>, token:<name>); a team means every member of it, whatever their role.

Nobody else sees a Spotlight in lists, on the Spotlights page, in the rail or through an AI assistant: administrators see them all, a team sees its own team Spotlights, and everyone else sees only those given to them, a group of theirs or a team of theirs. Someone with nothing but a Spotlight given to them can sign in and sees just its certificates. Spotlights made before this change keep working for their teams, but a view nobody was given is now seen only by administrators until one is given out. Changes are recorded in the Keeper's Log as spotlight-access; GET/PUT /api/spotlights/{id}/access and GET /api/spotlights/access do the same for scripts (administrators only).

My Spotlights is built in for everyone, beside All and Needs me: the certificates you enrolled yourself, since a certificate you enroll is owned by you as a person as well as by any team you chose. It needs no setting up. Its rule, owner:me, works in the search box too, but not in a saved Spotlight, where "me" would mean whoever is looking.

The search box, spotlights and team Spotlights use the same rules:

Rule Matches
tag:public certificates tagged public
owner:web / owner:none an owner whose name contains "web" / no owner
issuer:lab issuer name contains "lab"
subject:O=Example subject contains the text
san:10.0.0.5 a subject alternative name contains the text
template:WebServer issued through that template: the ADCS template in the CA's row (request 12 (WebServer)), or the Lighthouse template it was enrolled through (enrolled by alice (Web servers))
domain:example.com a DNS name equal to or under example.com
source:endpoint, file, ca, mac, cloud where it was seen
key:kept a private key is kept
keytype:rsa-2048, keytype:ecdsa key type
keysize<2048, keysize>=3072 key size in bits (<, <=, >, >=, =)
weak:any, weak:crypto, weak:key, weak:hash, weak:lifetime flagged weak (see Weak certificates); weak:crypto is a weak key or signature
state:active, state:expired, state:revoked, state:selfsigned in date and not revoked; past its end date and not revoked; revoked at its CA (revoked wins over expired); issued by itself (subject and issuer the same). The first three set the status themselves, so the Status filter steps aside
managed:no, managed:yes not issued by a CA Lighthouse manages (self-signed, or from a CA Lighthouse only found, such as a vendor's own root), or issued by one. A CA is managed when a template connects to it: Lighthouse read the certificate from that CA, or the issuer names the CA of an EJBCA or ADCS template, or DigiCert for a DigiCert template. managed:no is the built-in Self-signed Spotlight; Revoke is not offered on these, Replace with a managed certificate is
fact:Cost center=4410 / fact:Cost center that fact has that value (any case) / has any value
server:web01.example.com / server:*.example.com served by that host (or SNI name) / by any name under example.com
folder:D:\Certs\Web a file under that folder (a folder without a drive, like /certs, matches on any drive)
ca:Issuing CA issued by that CA, by the name shown under CAs
mac:Design-MBP-07 / mac:design-* / mac:* held by that Mac, by its name in Jamf Pro / by any Mac whose name starts with design- / by any Mac
cloud:azure-key-vault, cloud:aws-acm, cloud:google-cm / cloud:Azure Key Vault example-kv / cloud:Azure Key Vault example-kv/web-01 / cloud:* in any cloud vault of that kind / in that vault / that certificate in it / in any cloud vault; a trailing * matches every place that starts that way
expires<=30, expires<0, expires>90 days left; under the Active status nothing has fewer than 0, so use state:expired for expired ones
words name, issuer, place, tag, owner or fact value contains them

Join terms with and and put not before a term, for example tag:public and not owner:none and expires<=60.

Looking ahead and taking on

  • Adopt a site… (Automation, beside New installation, administrators): type an address such as shop.example.com or 10.0.0.5:8443. Lighthouse looks once, like a scan of one port, from this server or an Outpost; records the certificate with that place; says which Installation already keeps it there; and Keep it installed… opens New installation with the place filled in, named after the certificate's CN (changeable there and in New installation), and the types that fit the port first. POST /api/adopt, audited as adopt; its answer carries the certificate's full subject.
  • Adopt a server (the same dialog, Every IIS site on a Windows server, administrators): Lighthouse signs in to the server over WinRM, as an IIS Installation does (as Lighthouse's own account or the Sign in as account given, from this server or an Outpost), and runs the read-only script list-iis, which lists every https binding and the public certificate it uses. Each certificate is recorded with its place. Tick the sites, change names, pick a template, and one IIS Installation is made per site with POST /api/jobs. POST /api/adopt/server, audited as adopt-server. Closing a certificate opened from either view brings the dialog back as it was.
  • Where it lives (a certificate's drawer): one line from the CA that issued it, through every place it was seen and the Installations that keep it current there, to who owns it.
  • Forecast (a view on Certificates beside Table and Domains): a calendar of the next 90 days, year or two years, a square per day, darker for more certificates; green when every one renews by itself, blue when they wait for Run now, red when nothing renews them. It follows the open Spotlight and search and names the busiest weeks for people; a click lists that day. GET /api/forecast.
  • Ask in words… (beside the search box): a sentence becomes a rule with fixed patterns on this server, shown part by part before it is used. Ask the AI model appears only when Explain with AI is on and a model is set up; it sends only the sentence (host names hidden when the model setting says so) and is audited as ai-rule. POST /api/rules/from-words.
  • Undo and the plan: deleting a job, template, Spotlight, Pipeline, Recipe, profile, drill or saved report happens at once with Undo for 8 seconds and is sent only then (or straight away when the page closes). Cleanup, Replace all, moving to a new CA, giving many certificates their suggested owner and destroying a kept key show the plan first.

Picking a Spotlight on Certificates

Spotlights have no place of their own in the rail. They are listed beside the certificates on Certificates: Views (All, My Spotlights, Needs me, Expired, Needs an owner and Self-signed for administrators, Keys kept), Pinned, Teams' Spotlights and Shared views, each with its count, eight per group until Show more, and a Find a Spotlight box above them. Picking one dims the list with a moving bar straight away and shows only its certificates; picking, paging, sorting and searching keep the counts rather than counting every Spotlight again.

The open Spotlight shows above its certificates: its name, who owns it, Pin, Edit (administrators) and Save as new spotlight (administrators, when there is a search or a changed rule), and its rule as terms. The × on a term shows the view without it; + narrow it goes to the search box. A team's Spotlights are made and changed from its row on Teams too. Who sees and changes Spotlights is unchanged: administrators make and change them, and everyone else uses the ones they are given.

Manage Spotlights

Manage Spotlights (for administrators; All N Spotlights for everyone else), at the foot of the Spotlight list, shows every Spotlight you may use as one table in place of the certificates, with how each is doing: the team Spotlights of your teams and those an administrator gave you, a group of yours or your team (administrators see all). Back to the certificates or picking one shows the list again. Old links to #spotlights open it at #certificates/spotlights. It is made for tens or hundreds of them:

  • Health tiles count Spotlights by their worst certificate: Need action now (expired or due within 7 days), Due this month, Due within 90 days, All clear. Clicking one shows only those.
  • Find by name, rule or team (/ jumps to the box), and Sort by most urgent, name or size. The table shows 100 rows before Show more.
  • Counts leave revoked certificates out; a Spotlight whose rule names a status, such as state:revoked, shows its revoked ones as a separate number.
  • Each row has the Spotlight's owner, rule, count and a bar of its certificates by when they expire. A part of the bar opens just those certificates on Certificates; anywhere else on the row (or Enter) opens them all, and administrators change it with Edit.
  • The star pins it. Pins are personal: they come first here, under Certificates in the rail (up to eight; until you pin any, the few that need attention most) and in their own group of the Spotlight list, and go when the Spotlight is deleted. The arrow beside Certificates in the rail folds them away; the fold is still saved as spotlights.

The counts come from GET /api/spotlights/health, worked out on the server over the certificates you can see, so Manage Spotlights and the rail stay quick with many Spotlights; every rule is counted in one pass over the certificates; pages other than Certificates no longer download the whole inventory for the rail.

Requests & approvals

Requests is where team members ask and administrators decide. Approving carries a request out straight away; rejecting needs a reason the requester sees.

A request can be refused by itself when nobody decides it in time: a template's Refuse a request nobody decides after days, a Pipeline Approve step's Refuse after days, and 7 days for a change to your own access or a key recovery. It shows as Expired, is written to the Keeper's Log as request-expire, and its requester is emailed. Requests shows when each waiting one will be refused. Two kinds of request are not about certificates: a change to someone's own access (decided by someone else who looks after that part of Settings) and a key recovery that needs a second person.

  • Enrollment that needs approval. Tick "Team requests need an administrator's approval" on a template. Team requesters then send a CSR (the browser-made PFX is not offered, because the key would have to wait somewhere) and the certificate is issued when an administrator approves.
  • Giving an owner (administrators only). Nobody else takes ownership of a certificate: a team gets one from an administrator, or because one of its team Spotlights matches it. An administrator chooses Give an owner on Requests (find a certificate with no owner by its exact DNS name, serial number or thumbprint), in Needs an owner or on the certificate itself, and gives Just this certificate to a team or person, or Everything like it with a rule (Lighthouse suggests its server, folder, CA and domains): the team gets a Spotlight with that rule, so what is found there later is the team's too. The rule must match the certificate and nothing another team owns. It is done at once, unless a Take ownership requested Pipeline makes it wait. Claims people asked for before 0.25.0 still wait on Requests for an administrator to approve or reject.

Needs an owner

When Admins are the intended owner. Some organisations mean their PKI administrators or support staff to own whatever no team owns. Then a "needs an owner" task would never clear. Tick Admins are the intended owner of unassigned certificates under Settings, Application, General (off on a new server). The Dashboard stops asking: Your work and Suggested next steps leave out the owner task (while it shows, the Give an owner step links to this switch), and the tile says Owned by Admins (same count, same Spotlight, no warning colour). Needs an owner stays on Certificates as an ordinary Spotlight. Nothing else changes: only administrators see these certificates, their alerts go to the default recipients, Pipelines and Installations renew them as before, and Reports still count them. Turn it off again to bring the task back.

Admins own what nobody else owns. A certificate with no owner given by hand and no team Spotlight that matches it is the administrators' by default: its Owner says Admins, by default, only administrators see it, and its alerts go to the default recipients (Settings, Notifications). Admins is not a team anyone manages: nobody is added to it and it gives nobody anything, because administrators already see everything. It is listed first on Teams for administrators, with Needs an owner as its one Spotlight. A certificate that really is the administrators' own goes to a team of theirs, so it stops waiting here.

Needs an owner is a built-in Spotlight on Certificates for administrators (rule owner:none), next to My Spotlights and Expired. It replaced the Unowned page, and old #unowned links open it. It is worked out from the inventory each time, so a certificate leaves it by itself once it has an owner: given here or on the certificate, or matched by a team Spotlight. While it is open, Certificates shows one more column, Probably whose: how many days since Lighthouse found it without an owner (red from 30 days), who asked the CA for it, and up to three suggested owners with the reason. The Status filter, search, sorting and paging work as on any Spotlight. The Dashboard has a Needs an owner tile and a line under Your work, and Teams a count; each opens the Spotlight.

Suggestions come from what Lighthouse already knows, strongest first:

Source Suggests
same-name The owner of another certificate for the same DNS name or subject, often the one before a renewal done outside Lighthouse
requester For ADCS (the requester name) and Lighthouse enrollments: that person's own owner, any team that lists the account as a member, or, for a computer account such as LAB\WEB01$, whoever owns other certificates on that server
email The team whose email address is in the subject
spotlight A team whose Spotlight names the certificate's domain (domain: or server:*.)
server Owners of other certificates on the same server, or in the same folder, unless more than 20 certificates share it (a load balancer or an import folder says little)
template The team that owns at least three quarters of the owned certificates from the same CA and template (three or more)
domain Teams with certificates under the same parent domain
ai An AI assistant, while AI owner suggestions are on (see above); shown after Lighthouse's own

Give to a suggested team gives the certificate to it at once (Mine when the suggestion is you). Not ours stops suggesting it to that team (POST /api/v1/claims/not-ours, audited as owner-not-ours). Give to… picks any team, or you, or everything like it (another person gets one through their suggestion, since people are not listed as teams), Give each its first suggestion gives each certificate to its first suggestion, and ticking several and choosing Add owner gives them all to one team.

Who sees the certificates that need an owner. Administrators only. Everyone else sees the certificates they own themselves (an owner named after their account, even one an administrator gave them), their teams' and those in Spotlights shared with them; someone given See certificates everywhere without being an administrator (the installer's viewers group, an auditor, a grant with no team) sees every certificate someone owns, but never an unowned one. That holds on Certificates, search, the Dashboard counts, Reports, the certificate API and the AI tools, and /api/v1/claims, /claims/find, /claims/suggestions and /claims/not-ours answer 403 to anyone but an administrator.

ADCS sync now keeps the requester in the CA reference (request 12 by LAB\alice (WebServer)); certificates synced before 0.20.0 get it at the next daily sync.

  • Revoking. Open a certificate and choose Ask to revoke… (team operators) or Revoke… (administrators), with a reason. Lighthouse revokes through the EJBCA CA of a template that matches the certificate's CA. Certificates from other CAs must be revoked at the CA. Revoking cannot be undone.

New requests are sent as the approval email and to the Teams and Slack integrations, with a link when the public address is set.

The approval email is set under Settings, Notifications, Approval email:

  • Send to: addresses separated by commas; empty uses the default recipients under Email (SMTP). Tick Also email the team a Pipeline names as approvers to add that team's email address.
  • Subject and Body, with placeholders: {id}, {kind} (Certificate request, Take ownership or Revoke), {what}, {requester}, {requestedAt}, {certificate}, {template}, {team}, {note}, {facts}, {pipeline}, {waiting}, {link} and {site}. A placeholder with nothing to say becomes a dash; an unknown one is refused when saving. Preview fills them in with an example request, and Back to the defaults puts the original text back.
  • Reasons to reject with, one per line. An approver who rejects picks one and can add details; the requester sees "reason: details".

Why there is no Approve. Nobody approves or rejects their own request, so a request you asked for yourself waits for someone else, even if you are an administrator. Such a request says so on Requests and in its dialog, with who decides it instead: another approver for its team or an administrator, or what a Pipeline waits for. It says the same when you do not approve requests for its team, or already gave one of a Pipeline's yeses. To try approvals in a lab with one administrator, an administrator can turn on Administrators may approve their own requests (for testing) under Settings, Application, General; the dialog links there. Leave it off in production.

{link} is the public address followed by #requests/<id>. It opens that request on Requests, after the usual sign-in, with what it asks for, the note, facts and Pipeline, and Approve and Reject for someone who may decide it. Anyone else sees the request without the buttons, or is told it is not one they can see. The email is sent again when a Pipeline moves a request to its next round of approval. Untick Send the approval email to stop it; Teams and Slack still get their notice.

A Pipeline (below) can change how a request waits: more than one approver, a named team's approvers, two rounds of approval, no approval at all, or a refusal. A request that waits on a Pipeline shows the Pipeline and what it is waiting for, such as "1 of 2 approvals from Security (step 1 of 2)", and nobody approves their own request or gives two of the yeses.

Approving your own request, for testing. Normally nobody approves their own request. In a lab with a single administrator, tick Administrators may approve their own requests (for testing) under Settings, Application, General. Administrators then see Approve and Reject on their own requests too; everyone else still cannot decide their own. A Pipeline that needs two yeses still needs two different people. The audit log shows the same name as requester and approver. It is off on a new server; leave it off in production.

Self-signed: certificates Lighthouse can replace but not revoke

Self-signed is a built-in Spotlight on Certificates for administrators, owned by Admins (rule managed:no). It holds every certificate no CA Lighthouse manages issued: a certificate issued by itself, and one from a CA Lighthouse only found, such as a vendor's own root that signs an appliance's certificates (CipherTrust Root CA from Thales, say). Strictly the second kind is not self-signed, but it is handled the same way: nothing Lighthouse is connected to can revoke it.

A CA is managed when a template connects to it, under Settings, Connections: Lighthouse read the certificate from that CA, or the issuer's common name is the CA an EJBCA or ADCS template names, or the issuer is DigiCert and a DigiCert template is set up. Revoke asks exactly the same question, so the Spotlight and the Revoke button always agree. The server's copy of the certificate list works the answer out with the weak reasons, again whenever a template is added, removed or changes its CA; a certificate leaves the Spotlight by itself once a template connects to its CA.

  • Revoke is hidden on these certificates, and POST /api/v1/certificates/{thumbprint}/revoke answers 409 with the reason, before anything waits for approval. A Pipeline's Revoke step fails on them and says why. Revoke one at the CA that issued it, if anything can.
  • Replace with a managed certificate takes the place of Renew… on these certificates. It is the same Renew: where an Installation serves the certificate, a new one from that Installation's template goes in, now or at its change window; anywhere else Enroll is filled in with the same names, and the person picks a template (one they may enroll through), then makes the key or sends a CSR. The new certificate is linked as the old one's next version. Approval follows the template as for any enrollment; "Renewals skip approval" does not apply, as the template changes.
  • Teams shows the Spotlight on the Admins row, beside Needs an owner. Other people never see the chip; an administrator who wants a team to look after its own can share a Spotlight with managed:no and owner:<team>.

Pipelines

A Pipeline says what happens when something happens to a certificate: when an event happens, and the certificate or request matches its rule, then its steps run top to bottom. They are a tab of Automation, Automation, Pipelines, and only administrators see it and make, change and order them (the API answers 403 to anyone else). The Manage Pipelines permission of 0.21 and earlier is gone.

Each event has its own ordered list. The first Pipeline that matches is the only one that runs, so put specific Pipelines above general ones. When no Pipeline matches, Lighthouse does exactly what it did before Pipelines: the template's approval box decides for enrollment, and claims and revokes wait for one approver unless the person may act directly. An upgrade therefore changes nothing until someone adds a Pipeline.

Event Kind Steps it can use
certificate.requested: a certificate through a template, new or renewal, in the console, the API, ACME, SCEP or EST Request Tighten the request, Approve, Wait for a ticket, Refuse, Tell
revoke.requested Request Approve, Wait for a ticket, Refuse, Tell
ownership.requested (Take ownership) Request Approve, Wait for a ticket, Refuse, Tell
certificate.issued, certificate.renewed (also an Installation putting a new certificate in place) Reacts Tell, Call, Wait for a ticket, Approve, Change owner, Set a Fact, Revoke, Wait, Stop
certificate.revoked Reacts Tell, Call, Wait for a ticket, Approve, Run Installations, Change owner, Set a Fact, Wait, Stop
certificate.expiring: each of the Pipeline's thresholds (or those of Settings, Application), once; reminders after the last if it repeats Reacts All of them, with Run Installations
certificate.weak: each weak finding (key, hash, lifetime), once Reacts All but Run Installations
certificate.found: seen for the first time by a scan, import, CA read or Mac Reacts All of them, with Run Installations
spotlight.entered: a certificate starts to match the Pipeline's Spotlight (checked on the hour; the first check only records who is in it) Reacts All of them, with Run Installations
schedule: every certificate in the Pipeline's Spotlight, on its days at its hour (UTC), once a day, up to 2000 a run Reacts All of them, with Run Installations
facts.changed Reacts All of them, with Run Installations
owner.changed, install.failed, request.waiting, request.approved, request.refused (also expired), key.recovered Reacts All but Run Installations
job.failed (names no certificate) Reacts Tell, Call, Wait for a ticket, Approve, Wait, Stop

Steps:

  • Approve (requests): the request waits until 1 to 5 different people say yes. With no team, whoever approves requests for the request's team decides; with a team, people who approve for that team do (approvers everywhere count too). Two Approve steps in a row are two rounds. Refuse after (1 to 90 days, optional) refuses the request when nobody decides that round in time, and tells the requester. A matching Pipeline with an Approve step makes the request wait even for someone who could otherwise act directly; one without lets it through at once, even on a template whose approval box is ticked. A waiting enrollment must be a CSR.
  • Refuse (requests): answers 400 with your message and " (Pipeline )". Always the last step.
  • Tell: team (the owning teams' email addresses, or the default recipients when none has one), email: one to ten addresses, integrations (every integration that is on and takes expiry alerts under Settings, Integrations), teams, slack, jira or servicenow (a ticket). On certificate.expiring and certificate.weak, One message per check sends each destination one list of every certificate the check found, in the wording of Settings, Notifications, Message wording (Teams gets the expiry card); Jira and ServiceNow always get one ticket per certificate. The integration must be added and on under Settings, Integrations; its own choices of what it takes do not apply, since the Pipeline names it. A request's Tell steps send when it arrives (or, for enrollment that does not wait, once it is issued).
  • Run Installations: asks the Installations that last put the certificate in place (from the audit log), or the one picked, to run now, as Run now does. Only for expiring, revoked and found, since an Installation's own renewal would otherwise set the Pipeline off again.
  • Change owner (owner): gives the certificate to a team (add), takes it from one (remove) or gives it to that team only (replace). A saved Assign team step reads as Change owner, add.
  • Call a web address (call): posts to a webhook saved under Settings, Integrations, Webhooks (the only addresses a Pipeline can reach), with POST, PUT or PATCH, signed and logged as webhooks are. With no body it sends the event as below; a body of your own is JSON up to 4000 characters with placeholders ({certificate.name}, {certificate.thumbprint}, {certificate.serial}, {certificate.subject}, {certificate.issuer}, {certificate.notBefore}, {certificate.notAfter}, {certificate.daysLeft}, {certificate.names}, {certificate.teams}, {fact.<name>}, {event}, {summary}, {actor}, {pipeline}, {run}, {lighthouse}), escaped for JSON. The answer's status and body (up to 4000 characters) are kept: {reply.status} and {reply.<field>} (dotted, such as {reply.result.sys_id}) in later steps, reply:2xx in Only when.
  • Wait for a ticket (ticket): opens a Jira issue or ServiceNow incident through Settings, Integrations and waits until it is done, looking every 15 minutes. Done carries on, refused (a Jira resolution of Won't Do, Won't Fix, Declined, Rejected or Cancelled; a ServiceNow state of cancelled) stops the run, and past Give up after (1 to 90 days) the step fails. For events that react only in this version.
  • Approve on an event that reacts: a request of kind pipeline on the Requests page, for the step's team or the certificate's first owning team; the run waits for it. Yes carries on; no, or nobody before Refuse after, stops it.
  • Set a Fact (facts): sets one Fact, with placeholders; empty clears it.
  • Revoke (revoke): revokes the certificate at its CA with a reason (unspecified, keyCompromise, affiliationChanged, superseded, cessationOfOperation), as lighthouse-pipelines. The Pipeline must set how many it may revoke a day.
  • Tighten the request (tighten, certificate.requested only): maxDays, minKeySize (as RSA strength: ECDSA P-256 counts as 3072, P-384 as 7680), maxNames and noWildcards, tighter than the template for the requests the Pipeline takes. A request that breaks one gets 400 with the reason. Vault and DigiCert issue the shorter life; another CA's request is refused when its template lives longer, saying which template to use, since Lighthouse cannot ask those CAs for less.
  • Wait for a ticket on request events: the request waits as an approval round that only the ticket can decide. Lighthouse opens the ticket on its first pass and looks every 15 minutes; done carries the request out as lighthouse-pipelines (ticket X), refused or past Give up after refuses it. A person's yes on that round is refused. In the Keeper's Log as request-ticket.
  • Ask the owner (ask): asks team (its email addresses), email: addresses, teams or slack a question with 2 to 4 answers, or to fill in a Fact, with a link to #answer/<token> (a random 64-character key). The run waits 1 to 90 days. The team's members (with a permission on that team), the addresses asked and administrators may answer, in the console or with POST /api/v1/answers/{token}; open questions are on Requests. Later steps branch with answer:<answer>, answer:* and answer:none (nobody answered in time) and say {answer}. A Fact answer is checked as the Fact's own values and set on the certificate. In the Keeper's Log as pipeline-answer.
  • Wait until it is served (served, after Run Installations): waits, looking every 5 minutes, until each Installation the run started has finished a run since then. All succeeded (put in place and proved served) carries on; one that failed (and so put the old certificate back) fails the step; past hours (1 to 168, default 24) it fails too.
  • Wait: holds the run for 1 to 2160 hours (90 days); the run carries on by itself.
  • Stop: ends the run, with an optional reason.

Every step takes Only when (a rule, plus reply:200, reply:2xx, reply:none and failed:yes); a step that does not match is skipped. Call, Wait for a ticket, Run Installations, Change owner, Set a Fact and Revoke can fail: If it fails stops the run (the default), carries on (failed:yes is then true for the next step), or tells someone and stops. Call, Wait for a ticket and Run Installations take Tries (up to 5), after 1, 5, 30 and 120 minutes.

A Pipeline of an event that reacts can say Then the next: after it runs, the next matching Pipeline below it runs too. It can limit its runs, Installations and revokes a day (UTC): past a limit, runs wait as Held and the default recipients of Settings, Notifications are told once a day; an administrator lets a held run go, stops a run, or tries a failed or stopped one again under Recent runs. Every save is kept as a version (the last 50); Restore saves an old one as the newest.

Overrides. Several Pipelines may share a template, Spotlight or event; the first that matches wins. GET /pipelines works out, per event (and Spotlight), which enabled Pipeline never runs because one above it, without Then the next, has a rule whose terms are all in its own (it takes everything this one would), and which ones each overrides (those below it whose rule is a looser version of its own). The list shows both. The check reads the terms literally; two rules that mean the same in other words are not spotted.

Pipelines as files. Export writes the chosen Pipelines as JSON (format lighthouse-pipelines, version 1), with team, Installation, webhook and Spotlight ids beside their names. Import maps those names to the server's own ids; a Pipeline with the same name and event is changed as a new version and keeps its on or off, any other is added off; a missing name or invalid Pipeline saves nothing. In the Keeper's Log as pipeline-import. Versions has Compare, showing each setting and step that differs between that version and the saved one.

Describe it. With Explain with AI on and an AI model set up, an administrator describes a Pipeline in a sentence and the model drafts it from the events, steps, teams, Spotlights, webhooks and Facts of this server (host names hidden when set). The draft is checked as a save would be, opened in the form switched off and never saved by the model. In the Keeper's Log as ai-pipeline-draft.

Before switching a Pipeline on, Test run walks one certificate through it as it is in the form and says what each step would do, sending nothing (or only its Tell steps, to an address you give), and Replay the last 30 days counts what it would have done from the Keeper's Log (expiring: threshold crossings; found: first seen; weak: today's weak first seen; Entered a Spotlight: those in it now), after the Pipelines above it took theirs. Start from a ready-made one makes fifteen common Pipelines from a few answers, saved off with their replay, and the page suggests the ones the inventory calls for.

Rules are Spotlight rules read against the certificate, or for certificate.requested against what the request asks for (its DNS names and addresses, key type, template, and facts), plus three terms:

Term Matches
team:Payments, team:none The team a request is for, or the teams that own the certificate (by assignment or team Spotlight); none
by:CONTOSO\alice, by:CONTOSO\Contractors, by:token:* Who asks, or a group they are in, as access grants match people; a trailing * matches names that start that way
via:console, api, acme, scep, est, renewal, installation, lighthouse How it came: renewal is Renew on a certificate; installation an Installation's run; lighthouse what Lighthouse noticed itself (expiring, found)

ACME, SCEP and EST clients cannot wait for a person, so a Pipeline that would make one wait refuses it with a message saying so. Add not via:acme (or scep, est) to its rule to leave them out. Facts are not known for these clients' requests, so fact: terms do not match them.

Try it on the page (POST /pipelines/try) shows which Pipeline a certificate, or a sample request (names, template, team, who and how), would take and which enabled ones above it did not match; nothing runs. Recent runs (GET /pipelines/runs) shows each run of a reacting Pipeline with one line per step. Runs keep the steps they started with, so changing a Pipeline does not change a run that is waiting.

Reacting Pipelines run on the server that runs the jobs, every 15 seconds from the audit log (like webhooks), on the hour for expiring and weak, and from each certificate's first-seen time for found. A certificate that an enabled Certificate expiring or Certificate weak Pipeline matches is that Pipeline's: an alert job left from before leaves it out, so its team is not told twice. A Pipeline added later reacts to what happens from then on, not to the history. What Pipelines do themselves is recorded as lighthouse-pipelines and never starts another run, and a Pipeline's own approval request never starts a request Pipeline. Every change to a Pipeline, every run and every refusal is in the Keeper's Log (pipeline-add, pipeline-change, pipeline-restore, pipeline-order, pipeline-delete, pipeline-run, pipeline-install, pipeline-revoke, pipeline-run-retry, pipeline-run-stop, pipeline-run-release, pipeline-refuse, request-approve-step).

Notifications

Set the SMTP server, sender and default recipients under Settings, Notifications (called Email & Teams in 0.10 and earlier; old #email links still open it), then click Send test email.

Below the email settings, each added integration has four switches: On, Expiring certificates, Failed renewals and Site changes (see Change watch), and a Send test button. Integrations are added on the Integrations page; until one is added it does not show here.

A message that cannot be sent (a test, an expiry alert, a failed renewal, a request or key recovery notice) is written to the Application event log as a warning and to the Keeper's Log as alert-failed, naming the SMTP server or the service's host only, never a webhook's path or a credential.

The Certificate expiring and Certificate weak Pipelines send through these settings; a Tell step to integrations uses every integration with Expiring certificates on.

Integrations

Settings, Integrations, Add integration connects one of each. What to make in each service, what to paste, and what its errors mean, step by step: Teams, Slack, Jira and ServiceNow (in the console: Help, Microsoft Teams setup, Slack setup, Jira setup and ServiceNow setup).

Integration What it gets What to give it Secret name
Microsoft Teams One card per alert run, plus request, key recovery and site change notices The Workflows webhook URL (template "Send webhook alerts to a channel") teams-webhook
Slack One message per alert run, plus request, key recovery and site change notices An incoming webhook URL (a Slack app with Incoming Webhooks for the channel) slack-webhook
Jira One issue per certificate (label lighthouse), one per failing renewal job, and one per site change Site address, project key, issue type (default Task), and an account email with an API token (Cloud) or no email and a personal access token (Data Center) jira-token
ServiceNow One incident per certificate, one per failing renewal job, and one per site change Instance address, a user allowed to create incidents through the Table API (for example with the itil role), its password, and an optional assignment group by name servicenow-password

Webhook URLs, tokens and passwords go to the secret store and are never shown again; leave the field empty to keep the saved value. Send test posts a test message, or opens a test ticket in Jira and ServiceNow that you can close. Removing an integration deletes its secret, except teams-webhook, which alert jobs may name.

Tickets: a certificate gets one Jira issue or ServiceNow incident, at the first threshold it crosses; it is not reopened at the next threshold. Incidents carry lighthouse:<SHA-256 thumbprint> (or lighthouse:job-<id> for a failed job) as the correlation ID, with urgency 1 when the certificate has expired or a renewal failed and 2 otherwise. A ticket that could not be opened is tried again on the next run.

Failed renewals: when an IIS renewal or SSH installation job fails after having succeeded (or on its first run), the integrations with Failed renewals on get one notice. A job that keeps failing is not reported again until it has succeeded once.

The server needs outbound HTTPS to the service: hooks.slack.com, your Atlassian site or Jira server, your ServiceNow instance, and for Teams the workflow's host (*.environment.api.powerplatform.com, or *.logic.azure.com for older workflows). If a test times out, check the firewall first. A proxy is taken from the system environment variables HTTPS_PROXY and NO_PROXY (restart IIS after setting them).

A refusal names the service and its answer, never the URL or a credential. Teams refusals add what the status usually means (400: the workflow could not read the card; 401 or 403: the URL is cut short or the workflow wants signed-in callers; 404 or 409: the workflow was deleted or turned off; 429: try again later), and so do Slack's (no_service, channel_not_found, channel_is_archived, action_prohibited and others). A 401 or 403 from Jira or ServiceNow says to check the user and token or password; from Slack it gives Slack's own reason, since a webhook has no password.

Upgrading from 0.10 or earlier keeps a Teams channel set up under Email & Teams: it shows as the Teams integration with the same webhook, on or off as it was, taking expiring certificates and not failed renewals.

Webhooks

Webhooks have no switch: nothing is sent until an administrator adds a webhook (see Optional features).

A webhook sends Lighthouse's events to a service of your own, such as a SIEM, a deployment pipeline or a CMDB, as signed JSON posts. Settings, Integrations, Add webhook adds one (at most 20) with a name and an https URL, and a Pipeline's Call a web address step decides which events go to it. The webhook's card lists the Pipelines that call it, and it cannot be removed while one does. On the first start of this version, each event a webhook took becomes a Pipeline named "Webhook " with one Call step (5 tries), at the top of its event's list and set to Then the next, so it still gets every event; a webhook that was off makes Pipelines that are off. Lighthouse makes the signing secret and shows it once; New signing secret replaces it, and the old one stops at once. Send test posts a webhook.test event straight away, once, and shows what came back. The API is /api/v1/settings/webhooks (administrators only).

The URL and the signing secret go to the secret store as webhook-<id>-url and webhook-<id>-signing, so they are protected like other secrets (and shared through the database with a group certificate). The page shows only the URL's host. Removing a webhook deletes both.

Lighthouse-Event Sent by a Call step of certificate Main details
certificate.issued A certificate is issued through a template the new one template
certificate.renewed A certificate is renewed through a template (by a person or a script), or by an IIS Installation the new one, for template renewals replaces (old thumbprint)
certificate.renewal_failed An IIS Installation could not put the renewed certificate in place - target (the binding), details
certificate.expiring Certificate expiring, once per threshold of the Pipeline; checked hourly the certificate threshold, daysLeft, locations
certificate.revoked A certificate is revoked at its CA the certificate reason
request.submitted, request.approved, request.rejected Request waiting, Request approved, Request refused when the request is about one request (id), kind
job.failed Any scheduled job run fails (every failed run) - job (id), name, result
key.recovered A kept private key is recovered the certificate details (the reason)

A renewal through a template sends both certificate.issued and certificate.renewed. Expiry follows the same rules as alert jobs: only the latest version of a certificate is reported (see Alerts below), and so are certificates only known from a CA unless "Alert on certificates only seen at a CA" (Settings, Application) is on. A Pipeline added later gets the certificates already past a threshold on its first check.

Every post:

POST /your/path HTTP/1.1
Content-Type: application/json; charset=utf-8
User-Agent: Lighthouse/0.37.0
Lighthouse-Event: certificate.revoked
Lighthouse-Delivery: 3f0c9a2e6b5d4c1f8e7a9b0c1d2e3f40
Lighthouse-Timestamp: 1760940000
Lighthouse-Signature: sha256=5d1c...e9

{
  "id": "3f0c9a2e6b5d4c1f8e7a9b0c1d2e3f40",
  "type": "certificate.revoked",
  "occurredAt": "2026-10-20T06:00:00Z",
  "lighthouse": "https://lighthouse.example.test",
  "actor": "EXAMPLE\\alice",
  "summary": "A certificate was revoked by EXAMPLE\\alice: keyCompromise through template Web servers (serial 1A2B)",
  "certificate": {
    "thumbprint": "9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08",
    "subject": "CN=www.example.test",
    "subjectAlternativeNames": ["DNS:www.example.test"],
    "issuer": "CN=Example Issuing CA",
    "serialNumber": "1A2B",
    "notBefore": "2026-01-01T00:00:00Z",
    "notAfter": "2027-01-01T00:00:00Z",
    "daysLeft": 72,
    "revoked": true,
    "keyAlgorithm": "RSA",
    "keySize": 3072
  },
  "details": { "target": "certificate 9F86...", "details": "keyCompromise through template Web servers (serial 1A2B)", "reason": "keyCompromise" }
}

actor is a person, an API token (token:<name>), or the Lighthouse service that did it (lighthouse-pipelines for expiry). Other events (Facts changed, Entered a Spotlight, Owner changed, Certificate found, Certificate weak) keep their Pipeline event name in the header. id is pipeline-run-<run>-<step>. details always has target and details, the audit log's own words, and the fields in the table; new fields may be added, so ignore those you do not know. id is the same on every try of one event, so a receiver can ignore repeats.

Checking the signature. Lighthouse-Signature is sha256= and the lower-case hex HMAC-SHA256 of <Lighthouse-Timestamp>.<raw body>, keyed with the signing secret. Compute it over the body exactly as received, compare in constant time, and refuse a timestamp more than five minutes from now. In PowerShell:

$hmac = [System.Security.Cryptography.HMACSHA256]::new([Text.Encoding]::UTF8.GetBytes($secret))
$mac  = $hmac.ComputeHash([Text.Encoding]::UTF8.GetBytes("$timestamp.$body"))
$ok   = ("sha256=" + -join ($mac | ForEach-Object { $_.ToString("x2") })) -eq $signature

In Python:

import hmac, hashlib
expected = "sha256=" + hmac.new(secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256).hexdigest()
ok = hmac.compare_digest(expected, signature)

Retries. Answer 2xx to accept. When the receiver cannot be reached within 15 seconds, or answers 408, 425, 429 or 5xx, Lighthouse tries again after 1 minute, 5 minutes, 30 minutes and 2 hours (five tries in all), signing each try afresh. Any other answer, such as 400 or 401, is final. A delivery that fails for good is logged as a warning and written to the Keeper's Log as alert-failed (channel webhook, host only). Recent deliveries on the page lists the latest 50 tries with their outcome.

How it works. The server that runs the jobs reads new Keeper's Log entries every 15 seconds and turns the ones above into events, so anything that is audited (by the console, the API, a job, or another server in the group) is sent. It starts from the newest entry the first time it runs, so history is not replayed. Tries still waiting when the service stops are lost; the event is in the Keeper's Log. The server needs outbound HTTPS to the webhook's host.

Keeper's Log

The Keeper's Log is Lighthouse's audit trail: who signed in, and who added, changed or deleted what, and every job run. It is a tab of Logs under Settings. Administrators, people who open Settings, Logs (Read the logs), and anyone with Read the Keeper's Log everywhere, such as the built-in Auditor profile, read it; GET /api/v1/audit answers 403 to anyone else. Nothing in it can be changed or deleted: not through Lighthouse, and not in the database either, where a trigger (AuditEvents_AppendOnly) rolls back any change or delete and the service account is denied UPDATE and DELETE on the table. Removing the trigger takes a database owner. To keep a copy elsewhere, turn on log forwarding and send the audit stream to your SIEM.

Sealed, so a change made outside Lighthouse shows. The server that runs jobs seals each entry, in order, about a minute after it is written: an HMAC-SHA256 over the entry and the seal before it, with a key Lighthouse makes once and keeps as the secret keepers-log-seal (DPAPI on one server; sealed to the group certificate in a server group), so the database alone cannot make new seals. Seals are kept in AuditSeals, which is append-only too. Check the log on the page (POST /api/v1/audit/check) checks every sealed entry and names any that was changed, removed or slipped in among sealed ones, so a database owner who drops the trigger and edits the log is found. The check itself is written to the log as keepers-log-check. Without the key (the secrets folder was lost, say) it says it cannot check instead of saying all is well. Log forwarding sends each entry with its seal, so the SIEM's copy can be compared too.

Action When
sign-in Someone signs in through Windows, AD FS, Okta or another provider; the details name the way in and the address. With Windows sign-in alone there is no session, so a visit after 8 quiet hours counts as a sign-in (and a server restart starts the count again).
sign-out Someone presses Sign out. Windows sign-in alone has nothing to sign out of.
sign-in-failed A provider refuses or fails a sign-in, Lighthouse refuses what it sent, or it cannot be reached. A wrong Windows password never reaches Lighthouse; Windows records it in its Security event log.
sign-in-refused Someone signs in who has no access yet (at most every few minutes per person).
first-sign-in The first time Lighthouse ever sees a person.
token-use An API token is used for the first time after 8 quiet hours. Each read is not recorded; every change it makes is.
settings-change Any settings page or feature switch is saved. Details are JSON with before and after; the console shows only the fields that changed. Secrets are never in it.
change The safety net: a change through the console or API whose route did not record itself, with its method and path.

Everything else, such as owner-save, template-delete, request-approve, revoke or key-recover, is written by the route that made the change. Choices that only touch the person making them (pins, their own Dashboard layout, closing the message) and opening pages are not recorded. Show on the page picks everything, sign-ins only or settings changes only.

Cleanup

Settings, Cleanup keeps the database free of clutter. It is for administrators only, every part is off until one is turned on, and once a day the server that runs the jobs removes what the parts that are on find (at most 10,000 of each kind per run; the next run carries on). The page shows, next to each part, how many would go now with its days, even while it is off. Run now runs the saved parts at once.

Part Starts at Removes Keeps
Expired certificates 90 days after expiry The certificate, with its places, owners, tags, Facts, owner suggestions and sent-alert records One still found on a server, in a file or on a Mac after it expired; one with a kept key (until the key is destroyed); one waiting on an approval request
Places no longer found 90 days Places a certificate has not been found at, and scanned endpoints left with nothing A CA's record. A server's place until the server has been scanned again without the certificate, so a server that stops answering or a scan that stops running keeps what it had
ACME, SCEP and EST requests 30 days Failed or abandoned requests, and those whose certificate has expired Requests for certificates still valid (renewals and ACME revocation check them), and the newest one with a CA chain per template (SCEP and EST hand that chain out)
Finished approval requests 365 days after the decision Approved, rejected, cancelled and failed requests Pending requests

A removed certificate's thumbprint is remembered (table RemovedCertificates), so a CA sync, which reads every certificate the CA ever issued, does not add it back the next night; the sync's summary says how many it left out. A scan, an import or an enrollment that finds it again adds it back and drops the thumbprint.

Each certificate removed is written to the Keeper's Log as certificate-cleanup, naming its thumbprint, with its subject, serial, issuer, names, owners, where it was found, when it was revoked and when it expired; each place forgotten as place-cleanup; each run as cleanup (the daily run only when it removed something, as lighthouse-cleanup). So what a certificate was is still there after it is gone from the inventory: search the Keeper's Log for its thumbprint. If a scan finds it again later, its history shows when the cleanup removed it. The Keeper's Log itself is never cleaned up; use log forwarding to keep a copy elsewhere. The server log already cleans itself up (Settings, Server Log, days kept), and kept keys are destroyed at the end of their own retention.

Server Log

Settings, Server Log (in Logs under Settings) shows what the server did and what went wrong, so a failed job, CA connection, sign-in, ACME, SCEP or EST request, Installation, email or Teams message can be looked into from the console instead of on the server. It is for administrators and people who open Logs (Read the logs); GET /api/v1/logs also answers anyone with See the server log. The Keeper's Log stays the record of who changed what; the Server Log is for finding problems, and old lines are deleted.

  • Levels: Trace, Debug, Info, Warning and Error. Info is the default: sign-ins, jobs starting and finishing, certificates issued and installed, settings changed. Warning is what did not work while Lighthouse carried on (a failed job, an alert that could not be sent, a refused ACME, SCEP or EST request, someone with no access, an unknown API token). Error is what broke (the database, an error answering a request). Debug adds each API request with its answer and time and the parameters each job runs with; Trace adds every page and file the browser loads.
  • Change it while it runs: Record from on the page (or PUT /api/v1/settings/server-log) applies at once, with no restart, and on the other servers sharing the database within a minute. ASP.NET Core's own messages (the sign-in handshake with AD FS, Entra ID or Okta, Kestrel, IIS) are kept from Warning up unless Include ASP.NET Core's own detail is ticked.
  • Filter and follow: by least serious level, part of Lighthouse (Jobs, CAs, Enrollment, ACME, SCEP, EST, Installations, Discovery, Email & Teams, Webhooks, Log forwarding, Sign-in, Requests, Keys, Server group, Settings, Web server), time range and text. Live adds new lines every two seconds. Download saves what the filters show (up to 100,000 lines) as a text file.
  • Audit entries are copied into the log as they are written, under their part of Lighthouse, so a failed install or refused enrollment sits beside the messages around it. Failures and refusals are warnings.
  • Where: each server writes daily files lighthouse-yyyyMMdd.log (one JSON object per line) in %ProgramData%\Lighthouse\logs, or logFolder in lighthouse.json. A file that reaches 20 MB continues in lighthouse-yyyyMMdd-1.log. Files older than the days kept (14 by default, 1 to 365) are deleted, and the oldest beyond 1 GB in all. Setup gives the service account Modify on the folder; if it cannot write there, the page says why and keeps the last 5,000 lines in memory. With several servers, each keeps its own log and the page shows the server that answered.
  • Secrets are never written. Lighthouse does not log passwords, tokens or keys, and every line (errors from CAs and libraries included) is cleaned first: values after password, secret, token or apikey, Bearer and Basic credentials, lh_ API tokens, JWTs and PEM private keys become [removed]. Request lines carry the path only, never the query string.
  • The Windows event log is unchanged: warnings and errors still go to the Application log under the source Lighthouse.

Log forwarding

Lighthouse sends what it records to the place your organization keeps logs: Splunk, a syslog receiver or any service that takes JSON over HTTPS. There is no switch to turn on first: choose Add log destination on Settings, Integrations (administrators only), or POST /api/v1/settings/log-destinations. At most 10 destinations.

Kind For Settings
splunk Splunk Enterprise or Cloud, HTTP Event Collector url (the HEC address; https://splunk.example.com:8088 is enough, /services/collector/event is added), token (the HEC token, sent as Authorization: Splunk <token>), optional index and sourceType
syslog rsyslog, syslog-ng, QRadar, ArcSight, Microsoft Sentinel through its syslog forwarder (AMA) host, transport (tls by default, tcp, udp), port (6514 for TLS, 514 otherwise), framing over TCP and TLS (octet counting per RFC 5425 by default, or newline), facility (16, local0, by default)
https Elastic, Datadog, Sumo Logic, Logstash, Cribl and others url, optional headerName (default Authorization) and token (its value: ApiKey … for Elastic, the API key with headerName DD-API-KEY for Datadog), format (json, an array per post, or ndjson, one object per line)

What each destination gets. Tick any of:

  • Server log (app): the messages the server writes to the Windows event log, from the destination's minimum level (Warning by default; Information or Debug shows more of what the server is doing, Trace everything). Framework messages (categories Microsoft.* and System.*) are sent only from Warning, so a low level does not flood the destination with one line per web request or database query. The level is the destination's own: it does not change what the event log gets.
  • Audit log (audit): every audit entry, whatever the minimum level.
  • Alerts sent (alerts): each expiry alert Lighthouse sent, with the certificate, the threshold and the channel it went to.

The event. Every event is one JSON object:

{"time": "2026-10-21T06:00:00Z", "stream": "audit", "level": "Information", "source": "revoke", "message": "alice: revoke certificate AB12… (keyCompromise)",
 "server": "LH01", "product": "Lighthouse", "auditId": 4711, "who": "alice", "action": "revoke", "target": "certificate AB12…", "details": "keyCompromise"}

source is the logger's category for the server log, the audit action for the audit log, and the channel for alerts. Server log events add category, eventId when there is one, properties (the message's named values, such as {"Range": "10.0.0.0/24"}), and exception and exceptionType when there was one. Alerts add alertId, thumbprint, subject, serialNumber, notAfter, threshold (days; 0 means expired) and channel. Any URL in a server log message is cut to its host, as a webhook's path can be its secret.

  • Splunk gets one HEC event per line with time (epoch seconds), host (the server), source lighthouse, sourcetype (lighthouse:app, lighthouse:audit, lighthouse:alerts, or the one you set), index when set, and the object above as event.
  • Syslog gets an RFC 5424 message: <PRI>1 TIMESTAMP SERVER Lighthouse PID STREAM - JSON. The severity follows the level (Critical 2, Error 3, Warning 4, Information 6, Debug and Trace 7), the MSGID is the stream, and the message is the object above, so receivers that parse JSON get every field. Over UDP a message longer than 8 KB is cut.
  • HTTPS gets a POST of the objects, as a JSON array or as NDJSON (application/x-ndjson).

Send test sends one event with stream test straight away and shows the error if it did not arrive. Each card shows, for the server that answered, how many events were sent, are waiting and were dropped, and the last error with when it is tried again.

Buffering and retries. Sending never holds up the server: a server log message is put in a queue in memory and the call returns, and a background service sends every 5 seconds in batches of up to 500. When a batch cannot be delivered (no connection, a timeout, a TLS failure, HTTP 408, 429 or 5xx, or 401, 403 or 404, which usually mean a token or URL to fix), it stays at the front of the queue and is tried again after 5 seconds, then 15 and 30 seconds, 1 and 2 minutes, then every 5 minutes. HTTP 400, 413 and 422 mean the receiver will never take that batch, so it is dropped and counted. Each destination keeps up to 10,000 events waiting (and the server log queue 10,000 messages); past that the oldest are dropped and counted, so a destination that is down for long loses the oldest events, not the server's memory. Queues are in memory: events waiting when the service stops are lost, but the audit log and alerts stay in the database. The first failure in a row is written to the event log as a warning; the forwarder's own messages are never forwarded, so a broken destination cannot feed itself.

Which server sends what. Each server sends its own server log, with its name in server. The server that runs the jobs also reads new audit entries and alerts every 5 seconds (from the newest one when a destination first asks for them, so history is not replayed) and sends them once for the group.

Secrets and trust. The URL and token of a Splunk or HTTPS destination are kept in the secret store (log-destination-<id>-url, log-destination-<id>-token), shared through the group certificate when there is one, and never shown again; the settings page shows only the URL's host. Put an API key in the token, not the URL, where the service allows it. TLS (HTTPS and syslog over TLS) checks the receiver's certificate against the server's Windows trust and the host name, as for any HTTPS connection Lighthouse makes; Splunk's HEC ships with a self-signed certificate, so give it one from a CA the Lighthouse server trusts, for example one issued through Lighthouse. Adding, changing and removing a destination is audited as log-destination-add, -change and -remove, without the token. The server needs outbound access to the destination's host and port.

Secrets

Passwords and webhook URLs never go in a job. Store them on the server, in an elevated prompt, and give the job the name:

lh secret set smtp-password
lh secret set teams-webhook
lh secret set digicert-api-key
lh secret set slack-webhook

The value is asked for without echo and saved with DPAPI (machine scope) in %ProgramData%\Lighthouse\secrets. Machine-scope DPAPI opens for any account on the server that can read the file, so setup closes the folder to everyone but SYSTEM, Administrators and the service account (inheritance from ProgramData removed). It does the same for sign-in-keys, ssh (read only for the service account) and the default logs folder; a logFolder of your own only gains the service account. An environment variable such as LIGHTHOUSE_SECRET_TEAMS_WEBHOOK overrides a stored secret.

With a group certificate (GROUP_CERT, see Two or more servers), add --settings "%ProgramData%\Lighthouse\lighthouse.json" so the secret is saved in the database for every server, sealed to that certificate. lh secret share with the same --settings copies the secrets this server kept in its folder into the database, leaving any the group already has.

Alerts

Alerts are Pipelines: Certificate expiring and Certificate weak under Automation, Pipelines. Every server has two ready-made ones, on from the start: Expiring: tell owners and Weak: tell owners, each telling the owning teams by email (the default recipients for what nobody else owns) and the integrations that take expiry alerts, in one message per check. Change them, switch them off, or add your own above them for a team, template or any rule; only the first that matches a certificate tells about it.

A Certificate expiring Pipeline has its own choices:

Choice What it does
Days left (thresholds) Up to 20 thresholds from 365 to 0 (expired), such as 90,60,30,14,7,3,1,0. Empty uses Settings, Application (60,30,14,7,0 by default). Each certificate starts a run once per threshold.
Then again every (repeatDays) After the last threshold, a reminder every 1 to 30 days, also after expiry, until it is renewed or revoked, for up to 90 days after it expired.
Check at (sendHour) The UTC hour to check; empty checks every hour. Also on Certificate weak.
Certificates only a CA knows (includeCaOnly) Include or leave out certificates only read from a CA; empty follows Settings, Application. Also on Certificate weak.
From days left (fromDays, on a step) The step runs only from that many days left, for escalation: the team at every threshold, a manager from 7, a ticket from 3. A run that waits stops by itself once the certificate is renewed (and the new one is in use) or revoked.

Moving from alert jobs. The first time 0.33.0 runs Pipelines, each expiry alert job becomes a Certificate expiring Pipeline of the same name with its thresholds, CA-only choice and send hour (from a daily HH:mm schedule), and each weak certificate check a Certificate weak Pipeline; a job that only ran on Run now gives a Pipeline that is off. The notices the jobs already sent are copied to the new Pipeline, so nobody is told twice, and a job's own "send to" address becomes the default recipients when those were empty. The job is switched off, says where it went, and is listed under Alert jobs from before on Pipelines until you delete it. A job with its own SMTP server or Teams webhook is left running, since Pipelines send through Settings, Notifications; the ready-made expiring Pipeline is then added off, so it does not quieten that job. Set up email under Settings, Notifications, switch the Pipeline on and delete the job. New alert jobs are not added any more. The move is in the Keeper's Log as alerts-moved.

Each certificate is told about once per threshold (or finding) and Pipeline. Only the latest version of a certificate is reported: one is skipped when a newer certificate that is not revoked covers all of its DNS names and IP addresses (or its common name, when it has none), even if the renewal changed the subject or added names. The exception is renewed but not installed: if a scan, file or Mac still shows the old certificate somewhere the newer one has not been seen, it is still reported, and the alert says where the new one is missing. Certificates only known from a CA are skipped unless the Pipeline (or Settings, Application) says otherwise. The weak certificate check and the weekly team summary use the same rule.

For Teams, create a workflow in the channel from the template "Send webhook alerts to a channel", and add it under Settings, Integrations (or store it with lh secret set teams-webhook). Alerts arrive as an Adaptive Card; expiry alerts sent as one message per check arrive as one card listing them. See Integrations for each integration step by step.

Owners are set in the certificate's details on the dashboard. An owner can be a person or a team mailbox.

Weekly team summary

Every team gets one message a week about the certificates it owns (by assignment or through its team Spotlights): what expired in the last 30 days or expires within 30 days, what was renewed or installed in the last 7 days, and which of its Installations are failing. It is on from the start, on Mondays at 07:00 UTC, and needs nothing beyond Settings, Notifications:

  • A team with an email address gets an email through the SMTP server under Settings, Notifications.
  • A team without one gets a post in the Teams and Slack integrations under Settings, Integrations. Jira and ServiceNow never get it.
  • A team with nothing to report, or with nowhere to send it, gets nothing that week.

Change the day and time, turn it off for everyone, or Send now under Settings, Notifications, Weekly team summary. Turn it off for one team, and preview what it would be sent, with Edit on Teams. It goes out from the server that runs jobs, once a week; a server that was down at the time sends it within 12 hours, and otherwise waits for the next week. Each summary sent is in the Keeper's Log as team-summary. The API has the same: GET/PUT /api/v1/settings/team-summaries, POST /api/v1/settings/team-summaries/send, and GET/PUT /api/v1/owners/{id}/summary.

Reports

Reports in the rail makes four reports from the inventory, each over every certificate, one team's or what one Spotlight matches:

Report For What it shows
Expiring soon Teams, operations Every certificate that expires within the days you type (presets 7, 14, 30, 60, 90; any number from 1 to 3650), with days left, owners, issuer, where it is used and whether it is weak. Optionally the ones that already expired (in the last 90 days, or earlier and still seen in use). Group by team or issuer.
Executive summary Leadership A forecast line, the numbers that matter (valid, expired but still in use, expiring within 7, 30 and 90 days, % owned, weak, renewed in the last 30 days), the top 10 risks and a row per team.
Ownership Administrators % owned, the certificates without an owner (soonest to expire first) and how many each team or person owns.
Crypto health Security % strong, weak keys, old signatures and long lifetimes (the rules under Settings, Application), the weak certificates, and which key types and signatures are in use.
Who has access Administrators, auditors of access Every grant, team member, shared Spotlight, template opened by name and API token, with what each gives, where, until when, and when each person was last seen. It is made from access, not certificates, so it has no scope. Only administrators and people who look after People see it, save it or schedule it.

Every report leaves out revoked certificates and, unless you tick Count replaced certificates, certificates a newer one has replaced (a newer certificate that is not revoked covers all their names, as for expiry alerts), so a renewal never shows as a risk.

The forecast line opens every report: Storm warning when a certificate still seen on a server, in a folder or on a Mac has expired, or one expires within 7 days; Choppy when one expires within 30 days; Calm seas otherwise.

Getting it out: Download CSV (one file with the title, the numbers and every table; every download is in the Keeper's Log as report-export) and Print or save as PDF (the browser's print dialog, with the rail and buttons left out; pick Save as PDF). Click any certificate in a report to open its details.

Saved reports and scheduled emails: Save keeps a report with its options and scope under a name, listed on Reports for everyone who has Reports. A saved report can be emailed daily, weekly (a day of the week) or monthly (a day from 1 to 28) at a time in UTC, to one or more addresses. The email has the forecast line, the numbers and the first 25 rows of each table, a link back to the report, and the whole report as a CSV attachment. Skip if empty (on by default) sends nothing when nothing needs anyone: no rows for Expiring soon, nothing unowned for Ownership, nothing weak for Crypto health, calm seas for the Executive summary. Send now sends it at once. Emails go out through Settings, Notifications from the server that runs jobs; one that was down at the time sends within 12 hours. Each send is in the Keeper's Log as report-send, and saving, changing and deleting as report-save, report-change and report-delete.

Who sees what: two permissions on People & Access, which only the Administrator profile has until an administrator gives them:

  • See reports (reports.view): opens Reports and runs reports. Given everywhere, over every certificate; given for some teams, over those teams' certificates only, and over the Spotlights the person may see.
  • Create and change reports (reports.manage, everywhere only): also saves, schedules, sends and deletes saved reports, over every certificate. A scheduled email has no one signed in to limit it, so it covers everything in its scope and only people trusted with every certificate set one up; they choose who gets it.

The API has the same under /api/v1/reports (see docs/api.md).

Auto-renew: Off, Notify only or Renew automatically

Every Installation has an auto-renew setting that decides what its scheduled run does when the certificate is due. Run now always renews, whatever the setting, inside the Installation's change window.

Setting A scheduled run when the certificate is due
Off Does nothing, not even a check. Only Run now renews.
Notify only Checks without changing anything, then tells the certificate's owners and the Installation's team by email (or the default recipients when none has an address) and the integrations with Expiring certificates on: a Teams or Slack post, a Jira issue or a ServiceNow incident. People are told once per certificate. The Installations page shows "Due: press Run now", and the Dashboard's Suggested next steps lists it, until someone renews it.
Renew automatically Renews, installs and checks the certificate, as described below.

It is set at three levels, and the most specific one that is set wins:

  1. Global, under Settings, Application. A new server starts at Notify only. A server upgraded from 0.14 or earlier that already has Installations starts at Renew automatically, so they keep renewing as before.
  2. A team, in the team's Edit dialog on Teams, for that team's Installations ("Use the global setting" by default). Needs Manage teams.
  3. One Installation, in its Edit dialog ("Use the team's setting" by default). Whoever may change the Installation may change this, including a team operator for the team's SSH Installations.

The Installations page shows which setting applies and where it came from, for example "Auto-renew: Notify only (team setting)". Changes are in the Keeper's Log (owner-auto-renew, settings-change, job-change).

Renewing without approval, and wildcards

Full automation needs an expiring certificate to get a new one with nobody waiting to approve it. Lighthouse uses what it has, plus two template switches and one setting.

  • Which certificates renew by themselves: the ones an Installation keeps, with auto-renew set to Renew automatically (above). Installation runs never wait for approval; the check is when the Installation is given its certificate. A certificate no Installation keeps has nowhere for Lighthouse to put a new one, so its owners get the usual expiry reminders. ACME clients renew by themselves.
  • Renewals skip approval (approve once): a template switch, for administrators. On a template that needs approval, a renewal goes through at once when nothing approved changes: the same template (the old certificate was enrolled through it), no new names, for the team or person that owns the old certificate, a key at least as strong, and the old certificate not revoked. Anything else waits, with the reason in the request's note. It covers Renew… on a certificate (Enroll filled in as a renewal), POST /certificates/{thumbprint}/renew, and Install a certificate with an approved certificate, after which the Installation keeps it renewed. Each skip is renew-no-approval in the Keeper's Log. A Pipeline with an Approve step always wins.
  • Wildcards always need approval: Settings, Application (on unless turned off). A certificate with * in its common name or any DNS name waits for a person's approval whatever the template, a Pipeline without an Approve step or Enroll without approval allow. ACME (at the order), SCEP, EST and lh enroll cannot wait, so they are refused and wildcard-refused is written, unless the template has Wildcards without approval (for certbot with DNS proof, say). The certificate a CA sends back is also refused when it has a wildcard nobody asked for.
  • Wildcard Installations: renew only when someone who may approve requests for the team presses Run now or Renew…, plans it, or starts a drill or CA migration (administrators). Scheduled runs and a Pipeline's Run Installations only tell people it is due, as Notify only does ("Notify only (wildcards need approval)"). An Installation that copies whatever is installed is checked on the request itself, so its run fails with the reason instead.
  • Team Linux Installations: a team operator can only give one a template that needs approval if they may enroll without approval, or through Install a certificate with an approved certificate on a template whose renewals skip approval. Saving it again unchanged is fine.

Change windows and planned installs

Many companies change servers only under change control. A change window says when new certificates may go in where Lighthouse installs them, so nothing is replaced without the team knowing.

Setting a window. A team has one, in its Edit dialog on Teams ("Change window for this team's Installations"; needs Manage teams). An Installation can have its own in its Edit dialog: leave it empty to follow the team, or type any for any time. A window is days and UTC times, such as sat,sun 02:00-04:00, weekdays 22:00-23:30 or mon-fri 22:00-01:00 (an end before the start runs into the next day). Several spans are separated by ;. Empty means any time, as before. The field reads back what it understood while you type ("Weekends 02:00–04:00 UTC. Next opens Sat 2026-10-31 02:00 UTC."). The Installations page shows each Installation's window, whether it is open, and any planned install.

What waits for the window.

Who puts a certificate in place Inside the window Outside it
Auto-renew (Renew automatically) Renews as before. A due certificate is planned for the window's next opening, and the team is told. One not due is left alone.
Run now, Renew on a certificate, Install a certificate Runs now. Administrators only, with a reason; the team is told. Everyone else plans it for the window.
A Pipeline's Run Installations step Runs now. Planned for the next opening, and the team is told.
CA migration: Move and Undo Runs. That Installation is skipped with the next opening; run Move or Undo again then.
Replace-All Drill Runs. Runs: a drill never waits.

Planning. Plan… on an Installation, or Renew… on a certificate, opens one dialog: at the next change window, at a time you pick (it must fall inside the window unless an administrator gives a reason), or now. An optional change ticket number (such as CHG0012345) goes into the messages and the Keeper's Log. One plan is kept per Installation; planning again replaces it, and Cancel plan removes it.

Who is told. The Installation's team and the owners of the certificate it replaces, by email (or the default recipients when none has an address), and the Teams and Slack integrations that take notices: when an install is planned, a reminder a day before (for plans made more than a day ahead), the result when it ran, a cancelled plan, and an install made outside the window, with its reason.

When it runs. The server holding the jobs lease runs a plan at its time, as the person who planned it (as Run now does), or as auto-renew. A plan runs once: it is taken before it runs, even with two servers. The certificate is issued when the install starts, with the key made where it is installed, never ahead of time; then the install is proved, and the old certificate is put back if the proof fails, as for every run.

Renew on a certificate. The certificate's details have Renew…. When Installations you may run serve the certificate (matched by where they install, as drills match them, or by an install that recorded it), the dialog lists them with their windows, and each gets a new certificate through the template it already uses. When none does, Enroll opens filled in with the same names.

Windows and plans are settings rows (change-window:team:{id}, change-window:job:{id}, install-plan:{id}), removed with their team or Installation; no migration is needed. The Keeper's Log records owner-change-window, install-plan, install-plan-cancel and install-outside-window.

Change watch

Every scan job compares what each site serves with its last scan, and reports two kinds of change:

  • A certificate nobody requested through Lighthouse. The site serves a certificate it has never served before, and Lighthouse did not put it there: it was not enrolled through a template (directly or by an approved request), and no IIS renewal or SSH installation job installed it. A renewal by Lighthouse is expected and is not reported; a certificate someone installed by hand is.
  • A certificate that is gone. The site served a certificate on the last scan, and now does not answer, or answers without TLS.

Each change is written to the Keeper's Log as site-change and sent once:

Channel What it gets
Email The teams that own the certificates involved (the new one and the one it replaced), or the default recipients under Settings, Notifications when no team with an email owns them. One email per recipient per scan.
Teams, Slack One post per scan listing every change, when Site changes is on for the integration.
Jira, ServiceNow One ticket per change, when Site changes is on. ServiceNow's correlation ID is lighthouse:site-<endpoint id>-<SHA-256 or gone>.

The first scan of an address and port is the starting point and sends nothing, so adding a new range does not flood anyone. A site that stays down is reported once, and a certificate that comes back to a site that served it before is not new. Change watch only runs in scheduled or Run now scan jobs on the server, not in lh scan; clear Change watch in a scan job to turn it off for that job. If no email or integration is set up, the changes are still in the Keeper's Log and the job's result.

Weak certificates

A valid certificate is weak when it breaks one of three rules, set under Settings, Application, Weak certificates:

Finding Default weak: term
Weak key: RSA shorter than N bits, any DSA key, or an elliptic curve under 256 bits 2048 weak:key
Signed with SHA-1 or MD5 on weak:hash
Valid for more than N days, from start to end 398 (the most public CAs may issue) weak:lifetime

Set a number to 0, or clear the box, to turn that rule off. weak:any matches any finding, and weak:crypto a weak key or a weak signature. Revoked certificates are never weak; expired ones are left to the Runway.

The Dashboard's Weak certificates panel counts each finding among valid certificates; a count opens Certificates with that rule as a Spotlight. The certificate's details list its findings. GET /api/weak-rules returns the rules to anyone signed in, the certificate list and details carry a weak array, and PUT /api/settings/weak-rules changes the rules (administrators only, audited).

The ready-made Weak: tell owners Pipeline (Pipelines, Certificate weak) tells the same places as expiry alerts: each certificate's owners by email (Settings, Notifications' default recipients when it has none) and every integration with Expiring certificates on, in one message per check; Jira and ServiceNow get one ticket per certificate. Each finding goes to each Pipeline once, recorded with the alerts (channel weak:pipeline:<id>), so a check with nothing new sends nothing, and a rule tightened later reports only what it newly finds. Like expiry alerts, it skips certificates already replaced by a newer one with the same names, and certificates only known from a CA unless the Pipeline (or Settings, Application) says otherwise. A weak certificate check job from before 0.33.0 became such a Pipeline (see Alerts).

Macs (Jamf Pro)

Macs from Jamf Pro is off on a new server; turn it on under Settings, Features (see Optional features). While off, the job kind is greyed out in Add job, adding or changing such a job answers 404, and one that is started anyway fails saying the feature is off.

A Read the certificates on Macs from Jamf Pro job asks Jamf Pro which certificates each Mac holds, as Jamf saw them at the Mac's last inventory, and records the Mac on each certificate Lighthouse already has. Those certificates then show Mac <name> under where they were seen, match mac: and source:mac rules, and count in Runway and alerts like any other. Nothing is changed in Jamf or on the Macs, and nothing is installed on them.

Jamf reports a certificate's serial number, SHA-1 fingerprint and expiry, but keeps no copy of the certificate. A certificate is matched by serial number and fingerprint. One Lighthouse has never seen from a CA, a scan or a file cannot be added from Jamf; the job's result counts those and names the first five (name, Mac, expiry), so you can add the CA that issued them.

To set it up:

  1. In Jamf Pro, under Settings, API roles and clients, add an API role with the privilege Read Computers, then an API client with that role. Enable it and generate a client secret.
  2. On the Lighthouse server, store the secret: lh secret set jamf-client-secret.
  3. Under Automation, add a job Read the certificates on Macs from Jamf Pro with the Jamf Pro address (such as https://example.jamfcloud.com) and the client id, and a schedule such as daily 05:00.

If Jamf refuses the client (HTTP 401 or 403), the job stops at once and says so; it tries again only at its next run.

Cloud vaults

Many certificates live in a cloud provider's own store, where load balancers, gateways and CDNs pick them up, and no scan can reach them. With Cloud vaults turned on under Settings, Features (it is in the Professional and Enterprise licenses), Lighthouse reads three of them and keeps certificates in them current:

Vault What Lighthouse reads What an Installation does Who makes the key
Azure Key Vault every certificate in the vault starts a new version with a CSR, has the template's CA issue it, and merges the certificate in Key Vault; Lighthouse only sees the CSR
AWS Certificate Manager (ACM) every issued, expired, inactive or revoked certificate in the region, of every key type imports the certificate with its key and chain, into the same ARN each time Lighthouse, in memory for that one install (see below)
Google Certificate Manager every certificate in the project and location that has one adds or replaces a self-managed certificate and waits for Google to finish Lighthouse, in memory for that one install

Reading. A Read the certificates in a cloud vault job reads every certificate into the inventory. A vault hands out the certificate itself, so certificates Lighthouse has never seen are added, not only matched. Each one's place is the vault and its name there, such as Azure Key Vault example-kv/web-01, AWS Certificate Manager 111122223333 us-east-1/<certificate id> or Google Certificate Manager example-project global/web-01, so cloud: and source:cloud rules, Runway, alerts and Spotlights include them. Nothing in the vault is changed.

Installing. An Install and renew a certificate in a cloud vault Installation keeps one certificate current through an enrollment template, like the other Installations: a daily check, auto-renew, Run now, Drills and CA migration. It reads the certificate in the vault and renews it when it has fewer days left than the Installation says, asking for the same names unless the Installation gives a subject. It proves the install by reading the certificate back from the vault and, if you give where it is served, by connecting there. Azure Application Gateway and Front Door can take hours to pick up a new Key Vault version, so give where it is served only for something that picks it up at once.

  • Key Vault names the certificate (letters, digits and hyphens); a name that is not there yet is added. Keys are made as exportable by default, because Application Gateway, App Service and Front Door read the certificate with its key; untick it for a certificate only Key Vault itself uses. An unfinished version from a run that stopped half way is dropped and started again.
  • ACM names the certificate by its ARN. Leave it empty to import a new certificate: the first run imports it with the Installation's subject and names, and Lighthouse saves its ARN so later runs replace the same one, which keeps it attached to its load balancers. Certificates ACM issued itself (public or private CA) are read but never replaced, since ACM renews them.
  • Certificate Manager names the certificate by its id; one that is not there yet is added. Google-managed certificates are read but never replaced.

Where the key is made. Key Vault makes its own keys and never lets them out, which is the rule Lighthouse follows everywhere: the key is made where it is used. ACM and Certificate Manager cannot make a key for another CA's certificate; they only take one with it. For them Lighthouse makes the key in the server's memory when it asks for the certificate, sends it once to the vault over TLS with the certificate, and drops it. It is never written to the database, a file or a log, and Key Storage is not involved. A vault keeps no previous key Lighthouse could put back, so if proving a new certificate fails, it stays in the vault and the run says so.

Signing in. Each job signs in one of two ways:

  • With a stored credential: an Entra ID app registration's client secret (with the tenant and client id), an IAM user's access key (with its id), or a service account's JSON key file. Store it on the server with lh secret set <name>; the default names are azure-key-vault-secret, aws-secret-access-key and google-service-account-key.
  • As this server, when Lighthouse runs in that cloud: an Azure managed identity (give the client id only for a user-assigned one), the EC2 instance role (through IMDSv2), or the Compute Engine VM's service account. Nothing is stored.

What each needs, at least:

Vault To read To install too
Azure Key Vault Key Vault Reader role, or the access policy List and Get on certificates Key Vault Certificates Officer, or Create and Update on certificates
AWS Certificate Manager acm:ListCertificates, acm:GetCertificate acm:DescribeCertificate, acm:ImportCertificate
Google Certificate Manager Certificate Manager Viewer Certificate Manager Editor

If the cloud refuses the credential (HTTP 401 or 403, or AWS's access denied), the job stops at once and says what it needs; it tries again only at its next run.

To set one up:

  1. Make the credential (or give the server's identity the role) as above, and store it with lh secret set.
  2. Under Settings, Features, turn on Cloud vaults.
  3. Under Automation, add a job Read the certificates in a cloud vault with the vault and a schedule such as daily 05:30, and Run now.
  4. For a certificate Lighthouse should keep current, add an Installation Install and renew a certificate in a cloud vault with the same vault, the certificate's name and a template.

Certificate subject

Besides the common name, a certificate's subject can carry the organization (O), organizational unit (OU), locality or city (L), state or province (ST), country (C) and an email address (E). On Enroll, Make the key here (PFX) has all six under Subject fields, all optional, with a one-line summary of what goes in.

  • Defaults. Settings, Application, Certificate subject holds what Enroll fills in (GET /subject-defaults for anyone signed in, PUT /settings/subject-defaults for administrators; settings row subject-defaults). A template can set its own under Subject defaults on Enroll; an empty field there follows Settings, so a template can change only the OU. GET /templates gives each template's own subject and the effectiveSubject Enroll fills in.
  • Only defaults. People may change or clear any field before they enroll.
  • Checks. Up to 64 characters (128 for email), on one line; the country is two letters, saved in capitals; the email must look like an address and use plain ASCII.
  • CSRs keep their subject. With I have a CSR, the subject is whatever the CSR was made with: it is signed by its own key, so Lighthouse cannot change it.
  • The CA decides. An EJBCA end entity profile must allow each field (or EJBCA refuses or drops it), and an ADCS template may build the subject from Active Directory instead. If a field is refused, clear it on Enroll or have the CA administrator allow it.

Key types

An administrator decides which keys certificates may have; people who enroll never pick one. Under Settings, Application, Key types an administrator ticks the key types allowed anywhere and picks the default key type:

Setting Choices Starts as
Allowed key types RSA-2048, RSA-3072, RSA-4096, ECDSA-P256, ECDSA-P384, and the deprecated RSA-1024 and RSA-1536 the five standard ones, plus RSA-1024 or RSA-1536 if a template already listed it (so upgrades keep working)
Default key type one of the allowed ones Lighthouse can make (not RSA-1024 or RSA-1536) RSA-3072
  • Templates narrow it. Each template ticks the key types it takes and may choose the key Lighthouse makes for it; left on the default it follows Settings. A template takes only what both allow (usableKeyTypes on GET /templates). The template keeps its own list, so unticking a key type in Settings stops every template taking it at once, and ticking it again brings it back.
  • Every way in is checked. New certificate, the API and lh enroll, approvals, ACME, SCEP, EST, renewals and Installations all refuse a request whose key the template cannot take, before anything goes to the CA, and say which key types it takes and whether Settings or the template stopped it.
  • Enrolling. With Make the key here (PFX) the browser makes the template's key (madeKeyType) and the form shows which one, without a choice. With I have a CSR the device makes its own key and the form lists the key types the template takes.
  • Legacy keys. RSA-1024 and RSA-1536 are only issued where Deprecated cryptography (below) also allows them, and only from a CSR; Lighthouse never makes a weak key itself.
  • Recorded. The setting is the key-types settings row; each save is audited as key-types with the old and new choices. GET /key-types (anyone signed in) shows it and PUT /settings/key-types changes it.

Installations and renewal jobs on Windows and Linux servers still name the key their server makes; those are set by administrators on Automation, and the template must take that key type.

Deprecated cryptography

Lighthouse refuses to issue deprecated cryptography until an administrator allows it. "Deprecated" means what the weak certificate rules above flag, but never less than the safe minimum:

Refused Why it is deprecated Use instead
RSA keys shorter than the weak rule's shortest key, and never under 2048 bits 1024-bit RSA is within reach of well-funded attackers; public CAs stopped issuing it in 2013 RSA 3072 (or 2048, 4096)
DSA keys Withdrawn from FIPS 186-5; browsers and most CAs no longer accept it RSA or ECDSA
Elliptic curves smaller than P-256 Too little safety margin ECDSA P-256 or P-384
CSRs and certificates signed with SHA-1, MD5 or MD2 Practical collisions (MD5 2008/2012, SHA-1 2017) let a forged certificate share a real signature SHA-256 or stronger
SCEP messages with SHA-1 or MD5 digests, or DES or triple DES encryption Broken hashes, and ciphers NIST has withdrawn SHA-256 and AES

Every path. The check lives in one place, CryptoRules (src/Lighthouse.Server/Enrollment/CryptoRules.cs), and runs in EnrollmentService.CheckAsync before anything is sent to a CA, and again on the issued certificate. Everything that issues goes through it: Enroll in the console, POST /api/enroll and /api/v1, lh enroll, requests waiting for approval (checked when asked for and again when approved), ACME, SCEP, EST, renewals from the certificate page, auto-renew, Installations, IIS renewals that issue straight from EJBCA, and Replace-All Drills, for ADCS, EJBCA, Vault and DigiCert alike. A new issuing path calls EnrollmentService.EnrollAsync, or CheckAsync and IisRenewal.CheckIssued, and so gets the check without extra code.

What a refusal looks like. The person, script or device is told what was wrong and what to use instead, for example "Lighthouse refused this request because it uses deprecated cryptography: an RSA key of 1024 bits (the shortest allowed is 2048). Make a new key with RSA 2048 bits or more, or ECDSA P-256 or P-384, …". The API answers 400 with that error; ACME answers badCSR; SCEP answers a signed FAILURE with badAlg and the reason. If a CA itself signs with SHA-1, the certificate already exists: Lighthouse records it in the inventory (so it is flagged weak) but does not hand it out or install it, and the API answers 400 (422 for an Installation).

One meaning of weak. Raising the shortest RSA key under Weak certificates to 3072 also stops Lighthouse issuing 2048-bit keys. Lowering it, or turning the weak check off, never lets Lighthouse issue RSA under 2048 bits or SHA-1, MD5 and MD2 signatures: that takes the setting below.

Allowing it. Under Settings, Application, Deprecated cryptography an administrator ticks the templates it is allowed on, or Allow on every template and Installation, and gives a reason. Prefer a separate template for the old devices that need it. To issue RSA-1024 or RSA-1536 that template must also list the key type (RSA-1024, RSA-1536; they are accepted in Allowed key types only for this). DSA and curves under P-256 are not key types a template can list, and Installations always make strong keys, so those stay refused. SCEP's GetCACaps offers SHA-1 and DES3 only for templates where it is allowed. GET /api/crypto-policy shows the policy to anyone signed in; PUT /api/settings/crypto-policy changes it.

Audit. crypto-allow or crypto-strict with the reason each time the setting is saved (plus the usual settings-change); crypto-allowed for each certificate issued, or SCEP message accepted, only because of it; crypto-refused for each refusal.

Certificates already in the inventory are never blocked: scans, imports and CA reads still record them, and they are flagged by the Weak certificates panel and the daily weak certificate check.

Installations: the place, and the certificate it keeps

An Installation describes where a certificate goes: an IIS site or another Windows service, PFX or PEM files on Windows, PEM, PKCS#12 or JKS files on a server over SSH, a profile on an F5, Palo Alto or Citrix appliance, a certificate in a cloud vault, or a TLS secret in a Kubernetes cluster. It needs no CA settings of its own. Saved with its template on None yet, it only describes the place: each run, scheduled or Run now, reads what is installed there, records it in the inventory, and changes nothing. Drills and CA migrations list such an Installation as not ready until it keeps a certificate.

Under Certificate it keeps, a template (and optionally a subject and DNS names, and a key type) makes it keep a certificate renewed, as the sections below describe. These are the ways to give it one:

  • Install a certificate on the Installation: pick a valid certificate you can see. Lighthouse saves its subject and DNS names and the chosen template on the Installation and runs it now, so a new certificate with those names is issued, with its key made on the target, and kept renewed from then on.
  • Enroll, Install it into an Installation: pick the template on Enroll as usual, then the Installation and the names. The same happens.
  • It is there already: on Install a certificate, pick the certificate that is installed in that place now. Lighthouse reads the place first, as a Practice run does, and only when that certificate is there saves it on the Installation with the template; nothing is changed there and nothing runs, and the template renews it when it is due. Something else or nothing in place refuses and saves nothing. Every kind of Installation can do this, from an Outpost too, and no key moves.
  • As it is, with its kept key: a certificate whose key Lighthouse kept (a template that keeps keys, for PFX downloads) can be installed itself, with that key. Only the person who enrolled the certificate, or people with Recover kept keys for it, can do this; the unsealed key is used for that one install and cleared, and key-install goes in the Keeper's Log.
  • As it is, with a key you give: pick the certificate's PFX (or PKCS#12) file, or a PEM file with its private key, and type its password. The browser reads the file only for the request; the server opens it in memory, checks the key belongs to the certificate before anything is saved, uses it once and clears it. It is never stored (not in Key Storage, the database or a log); the Keeper's Log records that a key was brought, never the key or the password. It needs the rights to install a new certificate there, not Recover kept keys.

A certificate goes as it is, with its key, into files over SSH (the key goes into ~/.lighthouse/requests exactly where openssl would have made one, then moves into place like a renewal), IIS and the other Windows uses (imported into the machine store not exportable with Import-PfxCertificate, from a PFX made for that one install whose password reaches PowerShell outside the script's text, so script block logging never records it; Windows Server 2019 or later), and Azure Key Vault (imported), AWS Certificate Manager and Google Certificate Manager. Files on Windows, appliances, Kubernetes and Recipes make their own keys, and a private key never goes through an Outpost, so those get a new certificate or take over the one already there. The certificate installs while you wait, is proved in place, and the one before is put back if the proof fails. The chain is built from CA certificates the inventory already has. Later renewals use the template, with a new key made on the target. If nobody has the certificate's key (it was made where it cannot be exported, or lost), the certificate cannot be moved anywhere: install a new one with the same names.

Passwords for an Installation (Sign in as, the PFX password of Files on Windows, a keystore password, an appliance account) are typed in its dialog, twice, in masked boxes. On Save, Lighthouse stores the password under the Stored as name: in the server's secret store (PUT /api/v1/installation-secrets/{name}), or, for an Installation run from an Outpost, sealed in the browser to each of the Outpost's computers so Lighthouse never holds it. An empty name is filled in: signin-<account> for a sign-in, so other Installations signing in as that account just pick that name and type nothing, otherwise installation-<name>. The Stored as box takes only a secret name or a password manager reference and suggests the names in use. A typed password never replaces a stored secret that no Installation uses for a password, so it cannot overwrite Lighthouse's own secrets. lh secret set and lh-outpost secret set still work.

New installation starts with a card for each kind, with a search: IIS site, Windows service, Files on Windows, PEM files, Java keystore, F5 BIG-IP, Palo Alto, Citrix ADC, Azure Key Vault, AWS Certificate Manager, Google Certificate Manager, Kubernetes, and each Recipe. The kind is fixed once picked, and the form goes in four steps: Connect (the place and how to sign in; server names are suggested from where certificates were seen), Certificate, When (schedule, team, Run from, auto-renew, change window) and Check. Check it runs the Practice run's read-only checks on the unsaved form (POST /api/installations/check, which takes the same body as saving a job, refuses anything that is not an Installation and anyone who could not save it, and saves nothing). Save works from any step and goes to the first missing field. On a certificate's drawer, Keep it installed… opens the same cards with the place it was seen (a site or a file) filled in.

An Installation never waits for an approver, so a template that needs approval cannot be used for it unless the person may enroll without approval. POST /api/v1/jobs/{id}/install does all of this from scripts.

IIS sites set up before 0.26.0 with EJBCA settings of their own keep working; the form shows those settings only for them.

Renewing IIS certificates

An IIS renewal job, run daily, checks the site and renews when the served certificate has fewer days left than the job says, if its auto-renew is Renew automatically (Run now always renews). It:

  1. makes a new key on the web server (machine store, not exportable) and a CSR with the same names as the current certificate, or the subject and names in the job;
  2. sends the CSR through the Installation's template, to whichever CA it issues from (ADCS or EJBCA, for example), and checks what comes back (validity and every name) against the template's rules and longest validity. A CA migration can change the template. Sites set up before 0.26.0 may still send it to EJBCA with settings of their own;
  3. installs the certificate and its intermediates, binds it to the site, and checks the site serves it;
  4. puts the old binding back if it does not, and fails the run.

The account that signs in to the web server needs local administrator rights there and WinRM access when it is another machine: the Installation's own Sign in as account (signInAs and signInPasswordSecret, recommended; see Signing in as a separate install account), or else the service account. Issuing through EJBCA settings of its own needs an EJBCA role allowed to enroll with the chosen profiles. "Set up renewal" in a certificate's details fills in a job from where the certificate was seen.

Windows services and files

A Windows Installation picks what the server uses the certificate for under Use it for: an IIS site (above), or Remote Desktop, SQL Server, Exchange, AD FS, LDAPS on a domain controller, WinRM over https, or just the machine store (kind renew-windows). Files on Windows (kind renew-windows-files) writes a PFX or PEM files for an app that reads files, such as Tomcat, Java, nginx or a .NET app. Both reach the server as IIS does, with Windows PowerShell over WinRM as the Installation's Sign in as account or else the service account (or locally on Lighthouse's own server, or from an Outpost); the parameters travel as base64 JSON, never inside the script text. They are for administrators only. Run daily, each reads what is in use and renews when it is due and its auto-renew is Renew automatically (Run now always renews).

For the Windows uses, the key is made on the server in the machine store, not exportable (certreq, as for IIS); the certificate is accepted onto it and its intermediates go in the Intermediate store (never roots). Then the script windows-use points the use at it and proves it:

Use (use) What changes Who may read the key Restarts Proof
Remote Desktop (rdp) Win32_TSGeneralSetting.SSLCertificateSHA1Hash of RDP-Tcp NETWORK SERVICE none; new sessions use it reads the setting back (RDP is not plain TLS)
SQL Server (sql) the instance's SuperSocketNetLib\Certificate (instance, default MSSQLSERVER) the instance's service account the instance, then the services that depended on it and were running reads the setting back, and the instance must be running
Exchange (exchange) Enable-ExchangeCertificate for services (IIS, SMTP, IMAP, POP; default IIS,SMTP) Exchange's own Exchange's own port 443
AD FS (adfs) Set-AdfsSslCertificate, and Set-AdfsCertificate -CertificateType Service-Communications on the primary the AD FS service account adfssrv port 443 (give the federation service name as SNI)
LDAPS (ldaps) copies it into the NTDS service store and asks the DC to load it (renewServerCertificate) LocalSystem none port 636, and a TLS connection from the DC to itself
WinRM https (winrm) the existing HTTPS listener's CertificateThumbprint NETWORK SERVICE none port 5986
Machine store (store) the friendly name (label, default Lighthouse) moves to the new certificate; the old one becomes "(replaced)" the service's account, if one is named that service, if running reads it back, and servedHost if given

If the use does not take the new certificate, the script points it back at the old one before it fails; if the proof afterwards fails, Lighthouse runs the script again to put the old one back. Lighthouse never makes or removes an IIS binding, a WinRM listener or a certificate; old certificates stay in the store. For LDAPS, certificates that are only in the NTDS store (put there by hand) are left alone; if one of them keeps being served, the proof fails and the old state is put back. SQL Server and AD FS restart, so give those Installations a change window. Web Application Proxy servers are not covered yet; add one AD FS Installation per federation server.

For Files on Windows, the script windows-files makes the key on the server with .NET's CertificateRequest in the machine key store (Microsoft Software Key Storage Provider, named Lighthouse-<request id>), exportable only so the files can be written, and returns the CSR. When the certificate comes back it checks the certificate is for that key, writes the files beside where they go (<file>.lh-new), keeps each existing file as <file>.lh-previous (or marks that there was none with <file>.lh-none), moves the new ones into place, and restarts the service (and the services that depended on it). The PFX or the PEM key file is closed to everyone but administrators, SYSTEM and the service's account before the key is written into it, and the kept .lh-previous copy gets the old file's permissions. The key is deleted from the key store however the install ends. A PFX holds the key, certificate and intermediates, protected with the password from lh secret set <name> (at least 6 characters); PEM writes the certificate (optionally with the chain), an unencrypted PKCS#8 key guarded by those permissions, and an optional chain file. If anything fails after a file was replaced, or the proof fails, the previous files are put back and the service restarted. Kept keys never go to Windows: these Installations always get a new certificate with a key made on the server. The server needs .NET Framework 4.7.2 or later (Windows Server 2019 and later have it).

The certificate is recorded at the place //server/Cert:/LocalMachine/My/<use> (for SQL Server .../sql/<instance>, for the machine store .../<friendly name>), or for files //server/C:/path/file.pfx, and where it is served, so folder locations such as //sql01 give it to a team. A Replace-All Drill's dry run runs the scripts' check step, which changes nothing: the service, listener, instance or folders are there, the account may write to the machine store or the folders, and what is in use now.

The account that signs in needs local administrator rights on the server and WinRM access, as for IIS. These scripts were built and tested in the cloud only, against a fake WinRM shell; run each use once on a lab server before relying on it.

Signing in as a separate install account

An IIS site, another Windows use or Files on Windows can sign in as an account of its own instead of the service account, as Keyfactor and Venafi do: under Sign in as on the Installation, the Account (DOMAIN\name, name@domain, or .\name for a local account on each server) and the Password secret name. The password is a stored secret on the computer that does the work: lh secret set <name> on the server (DPAPI, or sealed to the group certificate on a group of servers), or the Outpost's Secrets for an Installation run from an Outpost, where it never comes to Lighthouse. Only the two names are kept with the Installation.

Lighthouse then always signs in over WinRM with Invoke-Command -Credential, even to its own server. A domain account uses Kerberos only, so its password is never offered to a computer outside the domain; a local account uses Negotiate (the server must be in TrustedHosts or have a WinRM https listener). CredSSP is never used. The password reaches the one PowerShell process in its environment, which takes it out at once: never on a command line and never in the script's text, so script block logging does not record it. A shell that cannot sign in as the account refuses, and an older Outpost refuses the new work kind powershell-signed-in, so an Installation that names an account never runs as anyone else. Use one account per group of servers, an administrator there and nowhere else; then the service account needs no rights on those servers. Every step and right is in The service account. Built and tested in the cloud only; try it on a lab server first.

Installing certificate files over SSH

An installation of kind "Linux or Unix files" keeps a certificate current on a server Lighthouse reaches over SSH, for Apache, nginx, HAProxy, Tomcat and other Java applications, or anything else that reads certificate files. Run daily, it reads what is installed and renews when it has fewer days left than the job says, if its auto-renew is Renew automatically (Run now always renews). It:

  1. makes a new key on the server with openssl (in ~/.lighthouse/requests, readable only by the SSH account) and a CSR with the same names as the installed certificate, or the subject and names in the job;
  2. sends the CSR through the job's enrollment template, so the template's CA, key types, domains and maximum validity apply, and checks what comes back;
  3. writes the files: PEM (certificate, key and an optional chain file, or the chain in the certificate file), a PKCS#12 keystore, or a JKS keystore built with keytool. Each file is staged beside where it goes, the existing one is kept as <file>.lh-previous, and the new one moved into place. Keys and keystores are mode 600, or 640 with the group in the job;
  4. reloads or restarts the systemd service in the job, through sudo -n if asked;
  5. reads the certificate back and, when the job says where it is served, connects there to check it is the new one;
  6. puts the previous files back and reloads again if the reload fails or the check does not see the new certificate, and fails the run.

The private key never leaves the server. Roots are not written to chain files; trust is the server's own.

To set it up:

  • On the Lighthouse server, install the OpenSSH client (built into Windows Server 2019 and later). Put the private key Lighthouse signs in with in %ProgramData%\Lighthouse\ssh\id_ed25519 and the servers' host keys in %ProgramData%\Lighthouse\ssh\known_hosts (for example ssh-keyscan web01.example.com >> known_hosts, then check the fingerprints). Give the service account read access to both, and no one else. Lighthouse never uses passwords for SSH and never trusts a host key it has not been given.
  • On each target, create an account (for example lighthouse) with that public key in ~/.ssh/authorized_keys and write access to the folders the files go in. It needs openssl 1.1.1 or later, and keytool for JKS. To let it reload a service without being root, allow only that command, for example lighthouse ALL=(root) NOPASSWD: /usr/bin/systemctl reload apache2 in /etc/sudoers.d/lighthouse, and tick "through sudo".
  • For a keystore, store its password on the Lighthouse server with lh secret set <name> and give the name in the job. It reaches the target inside the SSH session, never on a command line.
  • Point the service's configuration at the files once, for example SSLCertificateFile and SSLCertificateKeyFile for Apache, or the certificateKeystoreFile and certificateKeyAlias of a Tomcat connector. Lighthouse replaces a keystore as a whole, so give each installation its own file.

The installed certificate is recorded as the file //server/path, so a folder location such as //web01/etc/ssl gives it to a team.

Signing in: certificates, a stored key, or the server's key

Each Linux Installation picks how it signs in under Sign in with (signIn in the job's parameters). The full guide is Signing in to Linux servers.

  • A Lighthouse certificate (certificate, recommended). Lighthouse is its own SSH CA. Each run gets a key made in memory and a certificate for the Installation's account only, for 5 minutes, from the addresses under Settings, Connections, SSH sign-in, with no terminal, forwarding or agent. Servers trust it through one line, TrustedUserCAKeys /etc/ssh/lighthouse_user_ca.pub. SSH sign-in…, Switch this server over on the Installation sets that line through sudo -n with every change checked by sshd -t and undone on failure, proves a certificate sign-in, and only then switches the Installation; old keys are removed only when a person ticks them. An Outpost makes its own key per piece of work and asks the server to sign it.
  • A stored key (stored-key with sshKeySecret): this Installation's own private key, set with lh secret set <name> (or on the Outpost for one run from an Outpost). A team's must be named installation-….
  • This server's key (server-key, the default): the key in %ProgramData%\Lighthouse\ssh, as before.

Host keys can be accepted in the console (SSH sign-in…, Check host keys), by fingerprint, with the server asked again at the moment of accepting; servers can also be trusted through Lighthouse's host CA or your own, by one @cert-authority line. known_hosts in the ssh folder still counts.

Recipes: Installation types you write

A Recipe is the steps of an Installation for a place Lighthouse has no type for: an application that reads its certificate from a file of its own shape, a check its configuration needs before a reload, or a restart in a particular order. It runs as a Linux or Unix (SSH) Installation with Write as set to A Recipe, so everything an SSH Installation does still happens around it: the schedule and auto-renew, the template and its CA, the served-certificate check, drills, CA migration, Replace all and the inventory. Only administrators write, change, try and remove Recipes (Automation, Installations, Recipes).

A Recipe is a short sh script that defines three functions:

  • lh_read tells Lighthouse which certificate is installed now, usually with lh_read_pem <file> or lh_read_pkcs12 <file> <password>. With no file it prints nothing, and the first run installs.
  • lh_install puts the new certificate in place. The new key, certificate, chain and certificate-then-chain are in $LH_KEY, $LH_CERT, $LH_CHAIN (empty when there is none) and $LH_FULLCHAIN. It runs under set -e: any command that fails stops it.
  • lh_restore puts the old certificate back. Lighthouse runs it when lh_install fails or the new certificate is not what the server then serves, and the run fails with the reason.

lh_request makes the key and request on the server with openssl (in ~/.lighthouse/requests, readable only by the SSH account), so the private key never leaves it. A Recipe may define its own, for example to make the key inside an HSM's tool, as long as it hands the request back the same way, with lh_out_file csr <file>.

The helpers: lh_keep <file> and lh_put_back <file> keep and restore <file>.lh-previous; lh_write <from> <file> <mode> [group] writes beside the file and moves it into place; lh_pkcs12 <out> <password> [alias] builds a PKCS#12 keystore; lh_run "<what>" <command…> runs a command, through sudo -n when the Recipe has a ticked sudo value, and fails with its output; lh_fail "<reason>" stops with a reason Lighthouse shows.

Values. A Recipe declares up to 20 values each Installation fills in, and the script reads each as $P_<name>: text, a path (a full path, without . or ..), a secret (the name of a secret stored with lh secret set <name>; the script gets its value inside the SSH session, never on a command line) or yes or no (1 or 0). The first path is where the inventory records the certificate, as //server/path. Values never become part of the script text.

Presets. Start from fills a new Recipe from nginx (certificate and chain in one file, nginx -t, reload), Apache httpd (certificate, key and an optional chain file, apachectl configtest, graceful reload), HAProxy (one PEM with the certificate, chain and key, haproxy -c, reload) or Apache Tomcat (a PKCS#12 keystore with a stored password, restart). The Recipe gets a copy, so it changes only when you change it.

Versions and the Dry run. Saving a change to the script or the values makes a new version, and Installations do not run a version until it passes a Dry run: Lighthouse signs in to a server you name, runs lh_read, makes a key and request with lh_request, and removes them. Nothing the server serves changes and no certificate is issued. An Installation whose Recipe version has not passed one fails with the reason. Changing only the name, description or Teams may use it keeps the version.

Teams. Tick Teams may use it to let team operators who make their own Installations pick the Recipe; their secret values must be the team's own installation- secrets. Recipes in use cannot be removed.

Outposts. Recipes run from the Lighthouse server only. An Outpost runs only the scripts built into it, so an Installation that runs from an Outpost cannot use a Recipe.

Setting up a server is the same as for other SSH Installations (above), plus whatever the Recipe's own commands need, for example a sudoers line for nginx -t and systemctl reload nginx.

Installing on network appliances

Network appliances are off on a new server; turn them on under Settings, Features (see Optional features). An administrator then adds an Installation of kind On a network appliance under Automation, Installations, or POST /api/v1/jobs with kind renew-appliance. Team operators cannot add these: they carry an appliance administrator's password.

It works like an SSH Installation: run on its schedule, it reads the certificate the appliance's profile uses and renews when it is due and its auto-renew is Renew automatically (Run now always renews). The appliance makes the key and the CSR through its own management API, the job's template issues the certificate, the appliance installs it beside the old one, the profile is pointed at it, and Lighthouse reads it back and, when the job says where it is served (the virtual server's address), connects there. If either check fails, the profile is pointed back at the old certificate. Old certificates and keys are never deleted; remove lh-… objects by hand when you no longer need them. Replace-All Drills and CA migration include these Installations like any other; a drill's dry run signs in and reads the profile, nothing more.

Appliance API What the job names What Lighthouse does
F5 BIG-IP (f5) iControl REST A client SSL profile, and its partition (Common) sys crypto key with gen-csr; uploads the certificate and chain and installs them as lh-<time>-<name>.crt and …-chain.crt; points the profile's first certKeyChain entry at them (a second key type's entry is left alone); saves the configuration; with a device group, runs config-sync to-group
Palo Alto (paloalto) XML API An SSL/TLS service profile under Shared request certificate generate signed by external; imports the certificate with its chain under the CSR's name; sets the profile's certificate; makes a partial commit of the account's own changes and waits for the commit job
Citrix ADC (citrix) NITRO A certificate-key pair sslrsakey/sslecdsakey and sslcertreq in /nsconfig/ssl; uploads the certificate; updates the pair in place, so every virtual server and service bound to it changes at once; adds the issuing CA as pair lh-ca-<thumbprint> and links it; saves the configuration

To set it up:

  • Give Lighthouse an account on the appliance. F5: the Administrator role, because iControl REST hands out a certificate's or a CSR's text only through /mgmt/tm/util/bash, which Lighthouse uses with fixed commands (cat of the certificate file the BIG-IP names, tmsh list sys crypto csr) on names it checked. Palo Alto: an administrator with XML API and commit rights; Lighthouse commits only that account's changes, so other administrators' pending work is never committed with it. Citrix ADC: a command policy that allows the SSL, system file and save commands; requests sign in with headers, so no session is left open.
  • Store the password on the Lighthouse server with lh secret set appliance-password (or another name, given in the job). It is never part of the job.
  • If the management interface has a self-signed certificate, give its SHA-256 thumbprint in the job; Lighthouse then trusts that one certificate and no other for that appliance. Otherwise the usual checks apply against the server's trusted roots.
  • Citrix ADC needs a country, state and organization in every CSR, so give a subject with C=, ST= and O= (or install a first certificate that has them, whose subject is copied).
  • Point the virtual servers at the profile (F5, Palo Alto) or bind the pair (Citrix ADC) once by hand. Lighthouse changes what the profile or pair uses, not the bindings.

If the appliance refuses the sign-in (HTTP 401, or Palo Alto's Invalid Credential), the run stops at that call and tries nothing more. The installed certificate is recorded as the place //host/vendor/[partition/]profile, such as //bigip01.example.com/f5/Common/clientssl-www, so a folder location such as //bigip01.example.com gives it to a team.

These Installations were built and tested against fake appliances that answer as each vendor's API documentation shows. Run each one once against a real appliance (a vendor trial is enough) before relying on it.

Replace many

Settings, Bulk changes, Replace many is one page for replacing many certificates at once, for administrators. New replacement asks which CA's certificates (or any CA), optionally a Spotlight to take only part of them, and where the new certificates come from: the same CA makes a Replace-All Drill, another CA, through a template you pick, makes a CA migration. Both keep their own feature switch (drills, Professional; ca-migration, Enterprise), their own API (/api/v1/drills, /api/v1/ca-migrations) and their own steps below. The page lists both together, newest first, each tagged Same CA or Another CA. It shows once either feature is on; while both are off, administrators see the tab greyed with a Turn on button, and in the dialog a choice whose feature is off stays greyed. The old addresses #drills and #migrations open the page.

Replace-All Drills

Drills are off on a new server; turn on Replace many under Settings, Features (see Optional features). While off, /api/v1/drills answers 404, a drill job that is started anyway fails saying the feature is off, and Replace many offers only another CA.

A drill answers "if this CA had to go, or everything a Spotlight matches had to be replaced today, could we, and what would be in the way?" Administrators start one under Settings, Bulk changes, Replace many, with New certificates from the same CA, for one CA, one Spotlight, or a CA narrowed to what a Spotlight matches.

Its scope is every Installation whose certificate, where Lighthouse last saw it (the Installation's file //server/path, or the host and port where it is served), the CA issued (ca:<name>), the Spotlight's rule matches, or both (ca:<name> and <rule>, worked out again on every run). Installations Lighthouse has not yet seen serving anything are left out and counted, so scan or run them first.

  1. Dry run (runs as soon as the drill is started, and again with Dry run again). It changes nothing anywhere. For each Installation in scope it:

    • SSH files: signs in over SSH and reads the certificate file or keystore (with its stored password), as the renewal does first;
    • network appliances: signs in to the management API and reads the certificate the profile uses;
    • IIS: reads the certificate the site serves, and over WinRM checks the binding exists once and the service account can add to LocalMachine\My (script check-iis, read-only);
    • works out the subject, names and key type the renewal will ask for;
    • SSH files: puts a CSR for a key made in memory on the Lighthouse server and thrown away at once through the template's checks (on, CA on, key type and names allowed), so the CA is never asked for anything; IIS: checks the EJBCA client certificate is in the store and EJBCA answers TLS;
    • checks the template's CA answers TLS where the template reaches it (Vault, DigiCert or EJBCA), once per dry run; its sign-in is proved only by issuing;
    • where it is served, checks something answers there.

    Each Installation is Ready, or Not ready with the first thing in the way; hover a line for every check.

  2. Replace all is offered when a dry run finished in the last 24 hours, the Spotlight's rule has not changed since, no replace has used it yet, and at least one Installation was ready. It renews each ready Installation in turn through its own renewal job, exactly as its Run now does (a new key on the target, the certificate from its template or CA, installed and proved where it is served, the old one put back if not), reading what is in place before and after. Each line ends as:

    Result Meaning
    Replaced the new certificate is in place and proved
    Put back it failed, and the previous certificate is in place again
    Needs attention it failed, and what is in place is not the previous certificate: look at that server now
    Failed it failed before anything changed (for example the CA refused the request), so the previous certificate is still in place; or there was nothing before
    Not tried not ready in the dry run, removed since, or the drill stopped after the chosen number of failures in a row

    The Installation's own row shows the run as if Run now had been pressed. A replace happens once per press: the drill goes back to dry-run mode as the replace starts.

Like every job, a drill runs on the server holding the jobs lease, one job at a time, so other jobs wait while it runs. Its dry run and replace reports are kept with it (settings rows drill:<id>:dry-run and drill:<id>:replace) and removed with it; the Keeper's Log keeps drill-dry-run, drill-replace-start and drill-replace for good.

CA migration

CA migration is off on a new server; turn on Replace many under Settings, Features (see Optional features), with an Enterprise license. While off, /api/v1/ca-migrations answers 404, a migration job started anyway fails saying the feature is off, and Replace many offers only the same CA.

A migration moves the certificates one CA issued to a template on another CA: ADCS to EJBCA, EJBCA to ADCS, or between two CAs of the same kind. The old CA is an ADCS or EJBCA CA in Settings, CAs; the new template is an ADCS or EJBCA enrollment template that issues from a different CA. A Spotlight can narrow it, to move one team or one application at a time. Administrators start one under Settings, Bulk changes, Replace many, with Certificates from another CA and the template to move to.

Its scope is every certificate the inventory has from the old CA (ca:<name>) that is still valid and not revoked. Each Installation serves the newest certificate Lighthouse has seen where it installs (its file, or the host and port where it is served), so a certificate's Installations are the ones that serve it now.

  1. Readiness check (runs as soon as the migration is started, and again with Check again). It changes nothing anywhere. Each certificate is marked:

    Readiness Meaning
    Ready every Installation that serves it passed the drill's dry-run checks as if it already issued through the new template: reached over SSH or WinRM, the request worked out, the new template takes a CSR like it (on, its CA on, key type, names, deprecated cryptography) and the new CA answers
    Needs attention someone must act first where it is served: a server, the site or the CA could not be reached, or the request could not be worked out
    Blocked the new template or its CA would refuse it as things stand: a key type or name the new template does not allow, the new template switched off, or its CA turned off in Settings, CAs
    No Installation no Installation serves it, so Lighthouse cannot put a replacement in place and leaves it alone; to move it, add an Installation where it is used and check again, or enroll from the new template and install it by hand. Certificates with no Installation do not count against readiness: the job fails only when something is blocked or needs attention, and the migration shows "N of M ready" counting only the certificates it could move
    Moved its Installations issue from the new template already

    The check also lists each template the old CA issued through (from the CA's records, such as ADCS's template name, and every Lighthouse template that issues from the old CA) with the certificates it issued, and says what it allows that the new template does not: key types, names outside the new template's domains, a longer validity. Notes cover the whole migration: the new template is off, the old CA is turned off (its certificates can still be moved), or no template issues from the old CA (moving does not need one; revoking does).

  2. Move ready is offered when a check finished in the last 24 hours, no move has used it yet, and at least one certificate was ready. For each ready certificate, each of its Installations is pointed at the new template (an IIS Installation with its own EJBCA settings gets the template as Issue through; the EJBCA settings are kept for undo) and run, one at a time, exactly as its Run now does: a new key on the server, a certificate from the new CA, installed and proved where it is served, the old one put back if not. An Installation that fails is pointed back at what it had. A certificate is Moved when all its Installations moved; otherwise it is Put back, Failed or Needs attention as in a drill. Stops after the chosen number of failures in a row. The old certificates stay valid.

  3. Revoke old revokes the old certificates of moved certificates at the old CA, with reason SUPERSEDED, through the template that connects to the old CA (as Revoke on a certificate does). The console asks you to type how many it will revoke (confirm in the API). Before each one, Lighthouse reads every Installation it was moved on; if one no longer serves the certificate it was moved to, that one is not revoked and says why. Revoked certificates are marked revoked in the inventory and audited as revoke.

  4. Undo points every moved Installation back at the settings it had before the move (kept in the migration's record) and reissues from the old CA. Certificates from the new CA are left valid; old certificates already revoked stay revoked, and the Installations get new ones from the old CA.

Each step runs once per press: the migration goes back to its readiness check as the step starts. Like every job, it runs on the server holding the jobs lease, one job at a time. Its check and record are kept with it (settings rows ca-migration:<id>:assess and ca-migration:<id>:move) and removed with it; deleting a migration leaves the Installations as they are. The Keeper's Log keeps ca-migration-check, ca-migration-move-start, ca-migration-move, ca-migration-switch (each Installation pointed at a new template or back), ca-migration-revoke and ca-migration-undo.

Issuing on ADCS. Moving to an ADCS template issues as Lighthouse's own Windows account, so on the lab that account (the gMSA) needs Request Certificates on the CA and Enroll on the certificate template, as any ADCS template does. Revoking at an ADCS CA needs Issue and Manage Certificates.

Moving in and out

Move is off on a new server; turn it on under Settings, Features (see Optional features). It is for administrators, like all of Settings, since a move makes teams, profiles and grants. Settings, Move moves certificates and what people know about them into Lighthouse from Keyfactor Command or a move file, and out of Lighthouse to Keyfactor Command. Keyfactor's own names appear only as that side's name; everything arrives as Lighthouse's own things.

Every move has the same steps:

  1. Look reads both sides and changes nothing (only GET requests to Keyfactor Command, plus its certificate download for versions that leave the certificate out of the list). The report has one line per item: ready, check or no (it stays behind, and why). Ready covers anything that needs nothing more from anyone, including templates, Pipelines and scans that arrive switched off on purpose. Check is kept for a real issue: values that would be left out, people Lighthouse does not know, a Pipeline that loses part of what it did, keys the Look cannot confirm Command will hand over, or a collection whose query has no rule. Values are tried the way Copy tries them, so a fact is ready when every moving value fits it. Keys are ready when one of the roles of Lighthouse's Command account may recover keys (Certificates:Recover, or private key read in Command 24 and later); when its roles say no, they are check, and when Command does not show its roles (for example rights given through a group) they are check too, since Copy then finds out at the first refusal. A CA Lighthouse already has is ready. A collection of weak signature and short key tests (SigningAlgorithm -contains "SHA1" or "MD", KeySize -lt, with or without KeyType, joined by OR) becomes weak:crypto, Lighthouse's own weak key and signature test.
  2. Copy makes what Look showed and records each thing it made in the move (tables MoveRecords and MoveItems). Nothing that exists is overwritten.
  3. Switch (in only): moved certificates send no expiry or weak certificate alerts until a team that owns them switches, so nobody gets warned by both systems while both run. Switch each team when it works from Lighthouse.
  4. Finish keeps everything and ends the quiet time, or Undo removes what the move made, newest first.

What moves in

From Keyfactor Command Becomes in Lighthouse
Metadata fields Facts: string as text (its validation as the pattern), integer as number, date, boolean as yes/no, multiple choice as a pick list, email as text with an email pattern, big text as text. Required and hidden carry over. Look tries every moving value against the fact it lands in, as Copy does: the fact is ready when all fit, and check with how many and a few examples when some would be left out (longer than 400 characters, on several lines, not one of a pick list's choices, or not matching the pattern).
Certificates The inventory, with their facts, owners and where they are installed (//host/path sightings from the stores they are in). A certificate already here only gets the facts, tags and owners it lacks.
Owner roles Teams, with the role's people as viewers. An existing team of the same name is used as it is.
Collections Spotlights. A query that means the same as a rule (CN -contains, DNSName -endswith, IssuerDN, CA, TemplateShortName, KeySize, KeyType (also 1 and 3), NotAfter or ExpirationDate against %TODAY%, CertState 1 or 2, RevocationDate and SelfSigned (as state:), metadata -eq/-isnull, joined by AND, with NOT) gets that rule. A bracketed group of OR'd weak signature and short key tests (SigningAlgorithm -contains SHA1 or MD, KeySize -lt, with or without KeyType), as in a "weak encryption" collection, becomes weak:crypto, Lighthouse's own weak key and signature test with its thresholds from Settings, Application; a term every certificate meets, such as IssuerDN -ne NULL, adds nothing, so "All Certificates" matches every certificate. Anything else, such as OR, becomes a Spotlight on the tag moved-<name>, given to the certificates in it today, and the report says why. A query on %ME% depends on who looks and is listed for My Spotlights instead.
Templates Templates, switched off (ready), with key types, allowed domains (from plain name rules), approval and facts, for the CA of the template's forest (Command 10's ConfigurationTenant): the Microsoft CA Command reaches directly, or an EJBCA CA reached through a gateway when Lighthouse already has an EJBCA CA of that name (its profiles are read from Command's template name, end entity profile_certificate profile). Templates Command allows no enrollment with are left out and counted. Without forests (older Command), templates go to the only Microsoft CA.
Security roles Access profiles with the global permissions that match one allowing the same or less; the rest cover things Lighthouse does not have, so they are listed and the profile is still ready. Grants for the role's people only if Also give the people who hold each profile their grants is ticked. Rights on single collections do not move, and the report has one check line when a role has them.
SSL networks Scan jobs, switched off.
Expiration, issued and pending alerts Pipelines, switched off (ready, or check when something stays behind): Certificate expiring with expires>=0 and expires<=<days> and the collection's rule (or tag), Certificate issued, or Certificate requested for the alert's template, each with a Tell step to the same addresses (ten to a step). Recipients Command works out, such as {metadata:Email-Contact}, become the owning teams' email, and scripts stay behind; the report lists both. Expiry Pipelines are made nearest days first, since the first match runs. Denied alerts need no copy, since Lighthouse tells requesters itself (ready, built in). Lighthouse's own Alerts keeps running beside them, and leaves any certificate an enabled Certificate expiring Pipeline takes to that Pipeline, so nobody is told twice.
Workflows Enrollment workflows become Certificate requested Pipelines (for their template, or every template when global) and revocation workflows Revoke requested Pipelines, switched off: Require Approval becomes Approve with its number of approvals, and Email becomes Tell. Scripts, REST calls, conditions and Command's approver roles stay behind and are listed; a workflow with no step that matches stays a note.
Stores, orchestrators Installations are set up again, because Command never hands over store passwords or orchestrator accounts. A store Lighthouse can install to (IIS, PEM, JKS, PKCS#12) is ready: its certificates show where they are, and an Installation added under Automation lets Lighthouse install there. A store of a kind Lighthouse has no installer for stays behind. Orchestrators are not needed (WinRM and SSH). Store places are recorded by computer name, without WinRM addresses or orchestrator suffixes.
Private keys Command holds Nothing, unless Also bring the private keys Command holds is ticked. Then Copy asks Command for each one (Certificates/Recover, which needs Command's Recover permission and is in Command's audit log) and keeps it sealed to this server's key-encryption certificate, as kept keys always are, until its certificate expires or for the time picked. A key Lighthouse already keeps is left as it is, a 401 or 403 stops the asking, and Undo destroys the keys the move brought. Copy's own Keys line counts the keys that came, and is ready only when every key asked for came, check when some did not, and no when none did; it names the first problem, such as Command's own answer. The PFX password Copy asks Command for has upper and lower case, digits and a symbol, so a strict Command password policy accepts it. Without a key-encryption certificate, Look says the keys stay behind. Look marks the keys ready when one of the roles of Lighthouse's Command account may recover keys, and check when its roles do not allow it or Command does not show which roles it holds (rights through a group). A move file never holds keys.

What moves out

To Keyfactor Command, Copy makes metadata fields for the facts (plus Lighthouse-Tags and Lighthouse-Teams when anything has tags or teams), imports the certificates Command lacks (certificate only, never a key) and fills only metadata Command has empty, a collection per Spotlight whose rule has a Command query and one per team (Team <name>), a security role per profile with the matching permissions and its global grants, an expiration alert at 30 days per team with an email, and switched-off SSL networks for scan jobs. Objects Command has by name are kept as they are. Templates, CAs and kept keys stay behind.

Give a name prefix such as LH-move- for a trial, so everything made is easy to find. Undo needs the password or client secret again and removes what the move made in Command (metadata values it filled are emptied again), in the reverse order; anything already gone counts as removed.

The connection

Keyfactor Command's API address (https://<host>/KeyfactorAPI), and either an account and password (Basic) or an OAuth client with its token address (Command 11 and later). Every request says it comes from an API client (x-keyfactor-requested-with: APIClient, API version 1.0). The password or secret is used for that one request and never kept; a move keeps only the address, the way to sign in and the account. Server certificates are always checked against the Lighthouse server's trust. The first HTTP 401 or 403 stops the move and nothing more is sent. For moving in, an account that can only read is enough.

Move files

Save as move file writes what a move would carry as JSON (version 2, which adds pipelines; version 1 files are still read): from Keyfactor Command read now, or of Lighthouse itself. It never holds a private key, a kept key or a password. Moving in from a move file works the same way as from Keyfactor Command, so a file can be reviewed, kept, or moved in later or on another server.

Steps for a full move in

  1. Turn on Move under Settings, Features.
  2. Look with a read-only account (plus key recovery if keys should come). Fix what the report marks check where it matters to you (for example add the Microsoft CA under Settings, CAs first).
  3. Copy. Check the new teams, facts and Spotlights; turn templates and scans on when they are right.
  4. Switch each team as it starts working from Lighthouse; keep Keyfactor Command running until then.
  5. Finish, or Undo if you change your mind.

Look and Copy run inside one request. A very large Command (hundreds of thousands of certificates) may need a longer IIS request time-out; this is on the list for load testing.

Enrollment

Operators add enrollment templates on the dashboard: the CA (EJBCA, DigiCert, Vault or Microsoft ADCS) and how to issue from it, and the rules (key types, DNS suffixes, maximum validity). Then, under Enroll:

  • Make the key here (PFX): the browser makes the key the template uses (set by an administrator; see Key types), sends only the CSR, and builds a PFX protected by the password you choose. The password and key never reach the server.
  • I have a CSR: paste or upload a CSR made elsewhere, check it against the template, enroll, and download the certificate with its chain.

Common name as a DNS name

A certificate's common name (CN) is also one of its DNS names (subject alternative names), with nothing to turn on. Browsers and most TLS clients look only at the DNS names, so a certificate whose CN is missing from them fails in the browser.

  • When Lighthouse makes the request, the CN is always listed, first, and only once (compared without case): Make the key here (PFX), IIS renewals and Installations, Linux and Unix file Installations over SSH, and drills. Type only the other names under Other DNS names; typing the CN there as well does no harm.
  • Only host names count. The CN is added when it is a fully qualified DNS name with at least one dot, such as web01.example.com or *.example.com. A person's or device's name such as Ron F or printer01 is left as the CN only.
  • A CSR you bring (I have a CSR, the API and lh enroll, ACME, SCEP, EST) is signed with its own private key, so Lighthouse cannot change it. Vault and DigiCert templates ask the CA for the CN as well (Vault's alt_names, DigiCert's dns_names), and Lighthouse checks the certificate has it. EJBCA (pkcs10enroll) and ADCS issue the names the CSR lists; Check says when the CN is missing so the CSR can be made again with it. ACME clients list every name already, since the order's names must match the CSR.
  • Allowed domains cover the CN. A template's allowed domains now apply to a host-name CN even when the CSR lists other DNS names, so CN=evil.example.com with a DNS name in an allowed domain is refused.

Who sees which templates

A template shows on Enroll, and can be used, only by the people it is opened to. Administrators see and use every template. On the template, Who may enroll has two parts:

  • Teams whose requesters may enroll: a team's requesters and operators (and anyone given Enroll certificates for that team) see the template and enroll through it for that team. A team's viewers do not see it.
  • People and groups who may enroll for themselves, one per line: a named person, or anyone in a named group, sees the template and enrolls through it for themselves (the certificate is owned by them alone). Write them as in Teams and Access, whatever they sign in with: DOMAIN\name or name@domain (a person, or a Windows or AD FS group), group:<name or id> (a group or role an Okta or other OIDC sign-in sends in its groups or roles claim), user:<id>, or token:<name> for an API token.

Nothing else opens a template. In particular, an access profile with Enroll certificates given everywhere (with no teams) opens no template by itself; up to 0.28.0 it opened all of them. A template with nobody listed is for administrators only. The same rule decides personal ACME keys, team ACME keys, SCEP challenges, EST credentials and Installations, and the server checks it on every request, so a template someone may not use is neither listed nor accepted by POST /enroll.

The CA never sees the person: ADCS issues as Lighthouse's own account and EJBCA as its client certificate. So an ADCS template's Enroll permissions and EJBCA's roles do not decide who sees a template in Lighthouse; keep Who may enroll in step with them. For an ADCS template, Compare with the CA's Enroll list reads who has Enroll or Autoenroll on the template in Active Directory (as Lighthouse's own account, changing nothing) and lists who is only at the CA and who is only in Lighthouse. Someone only at the CA can still enroll there without Lighthouse.

A named person or group can be given until a date: add until 2026-12-31 at the end of their line. Names must belong to the team on a template lets a team's request, or a team's Installation, name only what the team's own Spotlights cover by name (domain:, server:, san: and subject: terms); a personal request keeps the template's allowed domains only.

Teams whose requesters may enroll on a template is a searchable picker, made for hundreds of teams. Click in the search box (or start typing) to see the teams in name order, narrow them by any part of the name, and click a team, or use the arrow keys and Enter, to add or remove it; the list shows 50 at a time and says how many more match. Chosen teams show as chips above the box: × removes one, Clear all removes them all, and Backspace in an empty box removes the last. Escape closes the list without closing the dialog. People & Access uses the same picker when a grant or an API token is only for some teams.

From the command line: lh enroll --template "Web server" --csr web01.csr --out web01.pem, with --fact "Cost center=4410" for each fact the template asks for.

EJBCA profiles

An EJBCA template names an end entity profile, a certificate profile and a CA. Pick the CA under Fill in from a CA and Lighthouse asks EJBCA, with the template's client certificate, which ones that certificate may use, and offers them as lists; typing a name always works.

  • EJBCA has no REST call that lists certificate profiles on their own. Each end entity profile names the certificate profiles and CAs it allows, so pick the end entity profile first and the other two lists narrow to it.
  • Lighthouse reads GET /v1/ca for the CA names, then GET /v2/endentity/profiles/authorized and GET /v2/endentity/profile/{name} for the profiles. Read only.
  • The CA list works on every EJBCA. The profile lists need EJBCA Enterprise (its /endentity REST resource) with REST End Entity Management turned on under System Configuration, Protocol Configuration, and the certificate's role needs to view the end entity profiles. Without them the dialog says so and the names are typed.
  • To make a new profile from Lighthouse, see New EJBCA profiles.

New EJBCA profiles

Administrators can make a new EJBCA end entity profile, with the names a certificate may carry, without opening EJBCA's admin web. Turn on Make templates on the CA under Settings, Features, then open an EJBCA CA under Settings, CAs and press New profile.

  • Start from an end entity profile EJBCA already has. The new one is a copy with only these changes: its name, the CAs it may issue from (the first ticked is the default), and how many DNS names (at least 1, since Lighthouse always puts the CN in as one; some of them required), IP addresses, email addresses and UPNs a certificate may carry. Other SAN kinds and every other setting stay as they were.
  • Validity (optional) also copies the certificate profile the start profile uses, under the same new name, with that validity, and the new end entity profile then allows only that one. Empty keeps the existing certificate profile. Use, keys and key usage always come from the certificate profile.
  • A certificate with a UPN can sign in to Windows as that user, so UPNs need the box confirming it.
  • In Lighthouse: Create also adds the Lighthouse template that issues with the new profile (the first ticked CA, both profiles and the CA's connection), so it shows under Templates & Policies and on Enroll straight away. Give it a name (empty uses the profile's name) and the teams and people and groups who may enroll through it; with none, only administrators see it. Untick Also add a Lighthouse template to make only the EJBCA side.
  • Preview says what will be made and changes nothing. Create in EJBCA makes it, and the Lighthouse template. If the Lighthouse template cannot be added (its name is taken, say), the profile stays made, the result says why, and Add a Lighthouse template opens Add template with everything filled in.
  • The profile it starts from is never changed. If a profile with the new name already exists, EJBCA refuses and nothing changes.
  • Every create, and every failed one, is in the Keeper's Log (ejbca-profile-create, ejbca-profile-create-failed) with what was made.

How Lighthouse does it. EJBCA's REST API has no call that creates profiles, but EJBCA Enterprise's REST ConfigDump exports and imports them. Lighthouse exports the start profile (GET /v1/configdump/EEPROFILE/{name}?defaults=true, and CERTPROFILE when the validity changes), changes only the fields above, and imports the copy (POST /v1/configdump?overwrite=abort&resolve=abort).

The profile certificate. Making profiles needs an EJBCA role that may edit them, which the read and enrollment certificates must not have. So each EJBCA CA can have a separate Profile certificate thumbprint (Settings, CAs, Edit), used only by Preview and Create here. In EJBCA:

  1. Issue a client certificate for Lighthouse's server (key made on the server, in the gMSA's or LocalMachine\My store) and put its thumbprint in the CA's Profile certificate field.
  2. Under System Functions, Roles and Access Rules, give it a role with: view and edit certificate profiles (/ca_functionality/view_certificate_profiles, /ca_functionality/edit_certificate_profiles), view and edit end entity profiles (/ra_functionality/view_end_entity_profiles, /ra_functionality/edit_end_entity_profiles), and the CAs the new profiles may use.
  3. Turn on REST ConfigDump under System Configuration, Protocol Configuration.

Without a profile certificate, or on EJBCA Community (no ConfigDump), Preview still works and lists the same changes as steps for EJBCA's admin web, and asks EJBCA nothing when no profile certificate is set. After the steps, Add a Lighthouse template opens the form filled in, since Lighthouse made nothing itself.

Lighthouse's enrollment certificate needs access to the new end entity profile too. A role that may use every profile has it already; otherwise add it to the role.

Autoenrollment from EJBCA

Windows computers and people can autoenroll from EJBCA, as they do from ADCS, through EJBCA Enterprise's Microsoft Auto-enrollment. With Make templates on the CA on, open an EJBCA CA under Settings, CAs and press Autoenrollment. The full guide, with every step and right, is docs/ejbca-autoenrollment.md.

  • Lighthouse makes the certificate template in Active Directory that Windows asks for: a copy of one there, with names from Active Directory (DNS name for computers, UPN or email for people), Read, Enroll and AutoEnroll for the groups you list, and published on no ADCS CA, so only EJBCA issues it. The same guard rails as New ADCS templates apply.
  • It makes the EJBCA end entity profile and certificate profile the template maps to: copies of the end entity profile you pick and its certificate profile, with one SAN field per name and the template's validity.
  • It then lists what EJBCA's REST API cannot reach: adding the Certificate Template Information extension to the new certificate profile, the mapping under EJBCA's Autoenrollment Configuration, and the Group Policy (Certificate Enrollment Policy with the policy server's address, and Auto-Enrollment). Before the first template lists the setup done once: MSAE on, the extension, the policy server's SPN and keytab, the alias, and trust in EJBCA's CAs.
  • Settings, CAs on the EJBCA CA holds the template account (an AD user with a password, never the gMSA; it needs only Create and delete certificate templates and OID objects), its password, an optional domain controller, the autoenrollment alias (default msae) and the policy server address (default https://<EJBCA host>/ejbca/msae/CEPService?<alias>). Without a template account, Preview gives the template as PowerShell for an AD administrator; without a profile certificate, the profiles come as steps for EJBCA's admin web.
  • Create makes the template, then the profiles; if EJBCA refuses the profiles, the template and its OID are taken away again. Every create, and every failed one, is in the Keeper's Log (ejbca-autoenrollment-create, ejbca-autoenrollment-create-failed).
  • No Lighthouse template is added: Windows asks EJBCA directly. Its certificates come into the inventory with the CA's read job.

New ADCS templates

Administrators can make a new ADCS certificate template, with its use, where its names come from, validity, key size and who may enroll, and publish it on the CA, without the Certificate Templates console. Turn on Make templates on the CA under Settings, Features, then open an ADCS CA under Settings, CAs and press New template. The full guide, with the template account's delegation for the AD team, is New ADCS templates.

  • Start from a template Active Directory already has. The new one is a copy, version 2 at least, with its own OID, and only these changes: its name and display name, its use (server, client, code signing, email), where the names come from (the request, or Active Directory with DNS name, UPN and email), validity, renewal period, minimum RSA key size (at least 2048), who may enroll (Read and Enroll for each group, and for Lighthouse's own account unless unticked), autoenrollment, manager approval, and whether it is published on the CA.
  • It refuses templates that would let someone get a certificate for another identity: a client certificate with names from the request that Domain Users or a similar group may enroll in without manager approval (ESC1), the same for any group unless confirmed, Any Purpose, enrollment agent or no use, autoenrollment with typed names, and CA templates. Nobody gets write, and the SID extension is always kept.
  • Preview reads Active Directory as Lighthouse's own account and changes nothing. Create in Active Directory writes as the CA's separate template account (Settings, CAs, ADCS, Template account and Its password; the password goes to the secret store). Without one, Preview gives the same as a PowerShell script for an AD administrator, with Copy script.
  • The template it starts from is never changed. If a step fails, what was made is taken away again. Every create, and every failed one, is in the Keeper's Log (adcs-template-create, adcs-template-create-failed).
  • In Lighthouse: Create also adds the Lighthouse template that issues with the new template, named after its display name unless you give another, open to the teams and people and groups you pick (none: administrators only). Its maximum validity is the new template's validity. Untick Also add a Lighthouse template to make only the AD side; if it cannot be added, the AD template stays and Add a Lighthouse template opens the form filled in.
  • Who may enroll at the CA is for requests that reach ADCS without Lighthouse (certreq, autoenrollment). Lighthouse requests as its own account, so who may enroll through Lighthouse is set under In Lighthouse.
  • Set up the template account shows whether the template account has each of its three rights in Active Directory, and the PowerShell that gives them and prints OK or MISSING for each.

Giving Lighthouse's certificate the rights to enroll

EJBCA cannot tell Lighthouse ahead of time whether a certificate may issue, so set the role up before the first enrollment. Test connection under Settings, CAs only proves reading, and says so. In EJBCA's admin web:

  1. Open System Functions, Roles and Access Rules.
  2. Find the role whose Members include the template's client certificate (Members shows the certificate's issuer and serial number). If that role is shared with other things, use Add Role to make one for Lighthouse and add the certificate under its Members.
  3. Click Access Rules for the role, then Advanced Mode.
  4. Under Regular Access Rules, allow /ca_functionality/create_certificate, /ca_functionality/view_certificate, /ra_functionality/create_end_entity, /ra_functionality/edit_end_entity and /ra_functionality/view_end_entity. Also allow /ra_functionality/revoke_end_entity if people revoke from Lighthouse.
  5. Under CA Access Rules, allow the template's CA.
  6. Under End Entity Profile Access Rules, allow the template's end entity profile.
  7. Click Save. The change applies to the next request; nothing needs restarting.

A certificate made only for EJBCA sync usually has a read-only role. Either widen that role as above, or give templates their own certificate.

When EJBCA refuses an enrollment

EJBCA answers HTTP 403 for two different things, and Lighthouse's error repeats what EJBCA said so you can tell which:

  • "This service has been disabled." The REST protocol is off. Switch on REST Certificate Management under System Configuration, Protocol Configuration (enroll and revoke use it; reading uses REST Certificate Management V2).
  • "... not authorized to resource /..." The role lacks the rule EJBCA names. The error says which Advanced Mode section it is under (such as Regular Access Rules for /ca_functionality/create_certificate) and lists every other rule enroll needs, so all of them can be fixed in one visit.

Lighthouse stops at the first refusal and does not retry.

"The CA issued a certificate valid for 730 days; the template ... allows 398." EJBCA issued, but for longer than the template's Maximum validity. Lighthouse keeps the certificate back, so nothing is downloaded, lists it under Certificates as refused for <you> and records enroll-refused in the Keeper's Log. Either raise the template's Maximum validity or shorten the validity in the EJBCA certificate profile, then enroll again, and revoke the refused certificate in EJBCA if you want it gone.

Public CAs

Lighthouse talks to DigiCert CertCentral itself; there is nothing else to install. Add a template under Templates & Policies and pick DigiCert CertCentral:

  • Product: CertCentral's product id, such as ssl_basic (OV), ssl_ev_basic (EV) or ssl_dv_geotrust (DV). Organization id (OV and EV) is on CertCentral's Organizations page.
  • API key: made in CertCentral under Automation, API keys, for a user who may order and revoke. It is saved in the server's secret store under the secret name (default digicert-api-key) and never shown again; leave it empty when editing to keep it. lh secret set digicert-api-key does the same from a prompt.
  • Domain validation: validate your domains once in CertCentral and orders issue at once. A domain that is not validated yet leaves the order pending: the enrollment says so, with the order number and DigiCert's token for the chosen method (DNS TXT by default), and the certificate arrives in the inventory with the next DigiCert sync once DigiCert issues it.
  • Maximum validity: public TLS certificates are limited to 200 days since 15 March 2026, so keep it at 199 or less. Lighthouse asks for that many days.

Enrollment, approvals and SSH installations work the same as with EJBCA. Add a DigiCert sync job to bring the account's existing certificates into the inventory; it uses the same secret. Revoking a certificate issued by DigiCert goes through a DigiCert template (DigiCert has no "privileges withdrawn" reason). Lighthouse turns off DigiCert's own renewal emails on its orders, since its alerts cover them.

More public CAs (ACME, Sectigo, GoDaddy)

Three more kinds can be added under Settings, CAs and picked for a template, each built in:

  • ACME CA: any CA that speaks ACME (RFC 8555), with Lighthouse as the client: Let's Encrypt, ZeroSSL, Google Trust Services, Sectigo's or DigiCert's ACME, or your own. Pick the directory from the list or type its https URL. CAs that need external account binding (ZeroSSL, Google, commercial CAs) give an EAB key id and HMAC key in their console; enter both. Lighthouse makes its ACME account key the first time it orders and keeps it in the secret store (acme-account-<host>); it signs Lighthouse's requests to the CA and is never a certificate's key. Choose how names are proved:
    • Cloudflare DNS (default): Lighthouse adds the _acme-challenge TXT record and removes it after. Give an API token with Zone, DNS, Edit on the zones (secret cloudflare-api-token).
    • DNS records added by hand: the first request answers waiting with the TXT records to add. Add them, wait until they show in public DNS, and send the same request again: Lighthouse picks up the same order (kept 7 days) and finishes it.
    • Files Lighthouse serves (http-01): the CA asks http://<name>/.well-known/acme-challenge/<token>; redirect that path on each name's web server to Lighthouse, which answers it while the order is open.
    • Already validated at the CA: for CAs that validate domains per account. ACME has no list of issued certificates, so nothing reads an ACME CA; its certificates come into the inventory as Lighthouse issues them. The CA chooses the validity (90 days at Let's Encrypt).
  • Sectigo Certificate Manager: the customer URI (the last part of your SCM address), an SCM administrator with API access, and its password (secret sectigo-password). A template picks the organization id, the SSL profile (the list comes from SCM) and the term in days. Orders SCM must approve, or whose domains are not validated yet, answer waiting with the SCM order id; the Sectigo sync brings them in once issued.
  • GoDaddy: the API key and secret from developer.godaddy.com, entered as key:secret (secret godaddy-api-key), and the customer id for reading. A template holds the product (DV_SSL by default), the period in years and the contact GoDaddy needs on every order. GoDaddy checks domain control itself, so orders are usually waiting at first; the GoDaddy sync brings them in once issued.

Revoking works for all three even with the feature off. A 401 or 403 from SCM, GoDaddy or Cloudflare stops at once and nothing is retried. These connectors were built from the published API references and tested against recorded answers only; try them against a test account first (Let's Encrypt staging, GoDaddy's OTE address https://api.ote-godaddy.com/, a Sectigo demo account).

HashiCorp Vault PKI

Lighthouse talks to a Vault PKI secrets engine itself, over Vault's HTTP API; there is nothing else to install. It reads the certificates the engine keeps, has Vault sign CSRs with one of the engine's roles, and revokes. Keys stay where they are made: only the CSR goes to Vault.

1. In Vault, give Lighthouse a policy. Change pki to the engine's mount and web-server to the role templates sign with. Leave out sign and revoke if Lighthouse should only read.

path "pki/certs"           { capabilities = ["list"] }
path "pki/cert/*"          { capabilities = ["read"] }
path "pki/sign/web-server" { capabilities = ["create", "update"] }
path "pki/revoke"          { capabilities = ["create", "update"] }

Then either make a token with it (vault token create -policy=lighthouse -period=768h, renewed by Vault or by you before it lapses) or, better for a service, an AppRole (vault write auth/approle/role/lighthouse token_policies=lighthouse, then read its role id and make a secret id).

2. In Lighthouse, Settings, CAs, Add CA, HashiCorp Vault PKI:

  • Vault address: such as https://vault.example.com:8200. The server must trust Vault's TLS certificate; if it is from a private CA the server does not trust, give the root as a PEM file under Vault TLS root PEM path.
  • PKI mount: where the engine is mounted, pki unless you mounted it elsewhere (such as pki_int).
  • Sign in with: Token (paste the token) or AppRole (the role id, and the secret id pasted as the credential; AppRole mount if it is not approle). The token or secret id goes to the secret store under Stored as secret (default vault-credential) and is never shown again; lh secret set vault-credential does the same from a prompt.
  • Namespace: Vault Enterprise and HCP Vault only.

Test connection lists the engine and reads the first certificate; nothing is changed. A refused token (HTTP 403) or a wrong AppRole secret id stops the read at once and is not retried until the next run.

3. To issue, add a template under Templates & Policies, pick HashiCorp Vault PKI, use Fill in from a CA, and give the Role. Vault applies the role's own rules (allowed domains, key types, longest TTL) as well as the template's, and Lighthouse asks for the template's maximum validity as the TTL. Enrollment, approvals and SSH installations work as with the other CAs. Revoking a certificate Vault issued goes through a Vault template; Vault keeps no revocation reason, so the reason is kept in Lighthouse's audit log.

Microsoft ADCS

Lighthouse requests from an ADCS CA itself, through the same Windows interfaces certreq and certutil -revoke use; there is nothing else to install. It sends the CSR with the name of an ADCS certificate template, as its own service account (the gMSA). Keys stay where they are made.

1. On the CA, give the service account rights. In the Certification Authority console, the CA's Properties, Security: Request Certificates to issue, and Issue and Manage Certificates if Lighthouse should revoke. On each certificate template it uses (Certificate Templates console, the template's Security tab): Enroll. The template must be published on the CA (Certificate Templates under the CA, New, Certificate Template to Issue). For web servers, the template's Subject Name tab should say Supply in the request, so the names in the CSR are used.

2. In Lighthouse, add the CA under Settings, CAs (Microsoft ADCS, with its config string HOST\CA Name), if it is not there already. Reading needs only Read.

3. To issue, add a template under Templates & Policies, pick Microsoft ADCS, use Fill in from a CA, and pick the Certificate template from the list the CA gives (or type its name, not its display name, such as WebServer). The ADCS template's own rules (validity, key usage, subject) apply as well as Lighthouse's, so keep the Lighthouse template's maximum validity at least as long as the ADCS template's, or Lighthouse refuses what the CA issues.

A request the CA holds for a certificate manager to approve is reported with its request id; once a manager issues it, it arrives in the inventory with the next ADCS read. A denied request shows the CA's own reason. Revoking a certificate the CA issued goes through an ADCS template for that CA (ADCS has no "privileges withdrawn" reason). Requesting and revoking need the server to run on Windows.

ACME clients

Lighthouse is an ACME server (RFC 8555), so certbot, cert-manager, lego, win-acme, acme.sh and Caddy can get and renew certificates by themselves through any template. Each template has its own ACME directory, https://<server>/acme/<template id>/directory, and every client needs an ACME key (ACME's External Account Binding), so nobody can open an account without one.

ACME is on on a new server, since nothing enrolls through it until someone makes an ACME key. An administrator can turn it off under Settings, Features (see Optional features); while off Enroll, ACME is greyed out, and /acme/... and /api/v1/acme-keys answer 404 saying where to turn it on. Turning it off again keeps the ACME keys and accounts, but clients get 404 until it is back on.

1. Open a template to the team. On the template, add the team under Teams that may enroll. ACME clients cannot wait for approval, so the template must not need it, and any facts it asks for need a default.

2. Make an ACME key under Enroll, ACME, New ACME key (an administrator, or someone they let make their own automation, see Who makes automation): what it is for, the team and the template. The dialog shows the directory, the key id and the HMAC key; copy the HMAC key, as it is shown only once. Lighthouse keeps it in the secret store as acme-key-<key id> (the database has only the key id). Requesters of a team may make and remove keys for their own team.

Personal keys. Someone who is not in a team, or whose server or tool is nobody else's, picks Just me (personal key) as the owner instead of a team. Certificates the key's clients get are owned by that person alone, as when they enroll for themselves in the console, and they are told before those expire. The template must be one the person may enroll through for themselves: they have Enroll everywhere (for example through a Requester grant on People & Access), or Enroll for a team the template lists. It still must not need approval. Only the person and administrators see a personal key, and only they can remove it. Through the API: POST /api/v1/acme-keys with {"name": "...", "personal": true, "templateId": 3}. An API token cannot make one; personal keys are for people.

When the person loses access. A personal key stops by itself, whatever the person signs in with. Lighthouse checks at every ACME order (and when a client opens an account) and once a day, in three layers:

  1. Their last sign-in, for everyone. At every browser sign-in (Windows, AD FS, Entra ID, Okta or any other provider) Lighthouse keeps with the person's personal keys who they are and the groups their sign-in carried. The check works out their access again from that with today's grants, teams and template. So an administrator taking away their grant, taking them off a team or closing the template stops the key for anyone; a group change at Okta or Entra counts from their next sign-in.
  2. Active Directory, for Windows and AD FS accounts. Lighthouse asks Active Directory (with the server's own account, the gMSA on IIS) whether the account still exists and is enabled, and for Windows sign-in which groups it is in now. A disabled or deleted account, or one taken out of the group that gave it access, stops the key at once, even if the person never signs in again. A locked-out account does not count.
  3. No sign-in, for everyone Active Directory cannot answer for. People of other providers, and anyone while Active Directory does not answer, stop after 90 days without a sign-in (making the key or an administrator turning it back on counts too). Administrators change the number at the bottom of Enroll, ACME (PUT /api/v1/settings/personal-acme-keys with {"inactiveDays": 60}). Someone deleted at Okta or Entra cannot sign in any more, so their key stops at the latest then; catching it the same day would need Lighthouse to ask Okta's or Entra's own API, which it does not do yet.

A key that stops is suspended: its clients are refused with a message saying why, the person no longer sees it, and the certificates it issued are no longer owned by the person, so they fall to Admins and come up in Needs an owner marked "owner lost access". The Keeper's Log records acme-key-suspend (by lighthouse-access-check, with the reason), and administrators are told through Settings, Notifications (its email recipients and the Teams and Slack integrations for notices). Under Enroll, ACME administrators see the key marked Owner lost access and choose: Give to team (the key becomes that team's key and the team owns what it issued; the template must list the team; POST /api/v1/acme-keys/{id}/give with {"team": 4}, audited acme-key-give), Turn back on (the person owns the certificates again and their access is checked again at the next order; POST /api/v1/acme-keys/{id}/resume, audited acme-key-resume) or Remove.

3. Point the client at it.

certbot certonly --standalone --server https://lighthouse.example.com/acme/3/directory \
  --eab-kid <key id> --eab-hmac-key <HMAC key> -d web1.example.com
lego --server https://lighthouse.example.com/acme/3/directory --eab --kid <key id> --hmac <HMAC key> \
  --email [email protected] --domains web1.example.com --http run

For cert-manager, an Issuer or ClusterIssuer with acme.server set to the directory, acme.externalAccountBinding.keyID to the key id and keySecretRef to a Secret holding the HMAC key; see cert-manager for a whole example.

How names are proved. Every name in an order needs a challenge before anything is issued. For http-01, Lighthouse fetches http://<name>/.well-known/acme-challenge/<token> on port 80, straight from the Lighthouse server (never through a proxy), so the server needs to reach the name on port 80. For dns-01, it looks up the TXT record at _acme-challenge.<name> with the server's DNS servers. Wildcards (*.example.com) need dns-01. Only names the template allows can be ordered; IP addresses cannot.

What is issued. The client's CSR is checked against the template (key types, domains, the order's names) and sent to the template's CA, as any enrollment. The certificate is recorded in the inventory, owned by the key's team (or the person, for a personal key), and audited under acme:<key id> (<name>). A new order by the same account for the same names is a renewal: the new certificate keeps the owners and tags of the one it replaces. Clients can revoke what they ordered (certbot revoke), through the template.

Removing a key closes its ACME accounts and their orders at once; certificates already issued stay. Taking the team off the template, switching the template off or making it need approval stops new orders with a message saying so. For a personal key the person's access is checked again as well, see below. The Keeper's Log records every key made (acme-key-create) and removed (acme-key-delete), saying whose it is (team Web or personal key of CONTOSO\alice).

The ACME addresses need no sign-in, as ACME requests are signed by the account's key. With IIS, keep Anonymous Authentication on for the site (the installer leaves it on). With two or more servers, give them a group certificate (see Two or more servers) so each has the HMAC keys; ACME nonces are kept in the database, so a load balancer needs no sticky sessions.

SCEP devices

Lighthouse is a SCEP server (RFC 8894), so phones, tablets, laptops, routers and MDM systems such as Jamf Pro can get certificates, for example for Wi-Fi or VPN, through any template. It is part of the Lighthouse server; nothing else is installed. SCEP is off on a new server; an administrator turns on Device enrollment under Settings, Features, which turns on EST too (see Optional features). While it is off, Enroll, SCEP is greyed out, /scep/... and /api/v1/scep-challenges answer 404, and turning it off again keeps the challenges. Each template has its own SCEP address, https://<server>/scep/<template id> (clients that add /pkiclient.exe by themselves are answered there too).

1. Open a template to the team. On the template, add the team under Teams that may enroll. SCEP devices cannot wait for approval, so the template must not need it, and any facts it asks for need a default. Devices usually ask for a name such as CN=ipad-0042, so use a template without allowed domains, or with the domain the devices' names are in.

2. Make a challenge password under Enroll, SCEP, New SCEP challenge (an administrator, or someone they let make their own automation, see Who makes automation): what it is for, the team and the template. The dialog shows the SCEP address and the password; copy the password, as it is shown only once. Lighthouse keeps only its SHA-256 hash. Requesters of a team may make and remove challenges for their own team.

3. Point the device or MDM at it.

scepclient -server-url https://lighthouse.example.com/scep/3 -challenge <password> \
  -private-key device.key -certificate device.crt -cn ipad-0042

That is micromdm's scepclient, which Lighthouse was tested with. Other clients, such as sscep, take the same address and read the password from the CSR's challengePassword attribute. In Jamf Pro, add a SCEP payload to a configuration profile with the SCEP address, a static challenge set to the password, and the subject and key size the template allows. Devices must trust Lighthouse's HTTPS certificate. Intune works with a static challenge only through profiles that let you type one; Intune's own one-time challenges need its validation API, which Lighthouse does not call yet.

What is issued. The device's CSR is checked against the template (key types, domains) and sent to the template's CA, as any enrollment. The certificate is recorded in the inventory, owned by the challenge's team, and audited under scep:<challenge id> (<name>). A device that sends the same transaction again, or polls for it, gets the same certificate. Renewal: a device renews by signing a new request (RenewalReq) with the certificate it has; no password is needed, as long as that certificate was issued over SCEP through the same template, is still valid, is not revoked and its challenge still exists. The new certificate keeps the owners and tags of the one it replaces.

Lighthouse's SCEP certificate. Lighthouse holds no CA key, so it answers as a registration authority: devices encrypt their requests to Lighthouse's own SCEP certificate (CN=Lighthouse SCEP, RSA-3072, self-signed, five years, made again a month before it expires), and it signs its answers with it. It is made the first time SCEP is used and kept, with its key, in the secret store as scep-ra; with two or more servers, give them a group certificate (see Two or more servers) so they share it. GetCACert hands out the SCEP certificate and, once the first certificate is issued through the template, the CA certificates that came with it.

Algorithms. Devices must sign with SHA-256 or stronger and encrypt with AES. GetCACaps offers SHA-256, SHA-512 and AES only, and a message signed with SHA-1 or MD5 or encrypted with DES or triple DES gets a FAILURE (badAlg) saying so, unless deprecated cryptography is allowed for the template (then GetCACaps adds SHA-1 and DES3). See Deprecated cryptography.

Removing a challenge stops its devices and the renewals of what it issued at once; certificates already issued stay. Taking the team off the template, switching the template off or making it need approval stops new requests with a message saying so (devices get a SCEP FAILURE whose failInfoText gives the reason).

The SCEP addresses need no sign-in, as every request carries a challenge password or is signed with the certificate it renews. With IIS, keep Anonymous Authentication on for the site. Clients send requests by POST, since Lighthouse lists POSTPKIOperation; a client that only sends GET can exceed IIS's default query string limit of 2048 characters. GetCRL and GetCert are not answered; CRLs come from the CA.

EST clients

Lighthouse is an EST server (RFC 7030, Enrollment over Secure Transport), so routers, VPN gateways, IoT devices and tools such as estclient can get and renew certificates over HTTPS through any template. It is part of the Lighthouse server; nothing else is installed. EST is off on a new server; an administrator turns on Device enrollment under Settings, Features, which turns on SCEP too (see Optional features). While it is off, Enroll, EST is greyed out, /.well-known/est/... and /api/v1/est-credentials answer 404, and turning it off again keeps the credentials. Each template has its own EST address, https://<server>/.well-known/est/<template id>/, the template id being what RFC 7030 calls the optional label (estclient's -aps).

Operation What Lighthouse does
GET cacerts The CA certificates that came with the last certificate issued through the template (over EST, SCEP or ACME). Before the first one it answers 404, as Lighthouse does not otherwise store CA certificates.
POST simpleenroll Issues a certificate for the client's CSR. Needs an EST credential (HTTP Basic).
POST simplereenroll Renews a certificate Lighthouse issued over EST through the template (below).
GET csrattrs 204: no attributes asked for; the template's rules say what is accepted.
serverkeygen, fullcmc 501. Clients always make their own key; Lighthouse never makes or holds it.

1. Open a template to the team. On the template, add the team under Teams that may enroll. Lighthouse answers EST clients at once, so the template must not need approval, and any facts it asks for need a default.

2. Make a credential under Enroll, EST, New EST credential (an administrator, or someone they let make their own automation, see Who makes automation): what it is for, the team and the template. The dialog shows the EST address, a user name made by Lighthouse (such as est-k3m9q2xa) and a password; copy the password, as it is shown only once. Lighthouse keeps only its SHA-256 hash. Requesters of a team may make and remove credentials for their own team.

3. Point the client at it.

estclient enroll -server lighthouse.example.com -aps 3 -user est-k3m9q2xa -pass <password> \
  -key router.key -cn router-1.example.com -dnsnames router-1.example.com -out router.crt
estclient cacerts -server lighthouse.example.com -aps 3 -out ca.pem
estclient csr -key router-new.key -cn router-1.example.com -dnsnames router-1.example.com -out router-new.csr
estclient reenroll -server lighthouse.example.com -aps 3 -certs router.crt -key router.key \
  -user est-k3m9q2xa -pass <password> -csr router-new.csr -out router-new.crt

That is GlobalSign's estclient (go install github.com/globalsign/est/cmd/estclient@latest), which Lighthouse was tested with. libest, Cisco IOS and other EST clients take the same address, user name and password. Clients must trust Lighthouse's HTTPS certificate.

What is issued. The CSR is checked against the template (key types, domains) and sent to the template's CA, as any enrollment. The certificate is recorded in the inventory, owned by the credential's team, and audited under est:<credential id> (<name>). The answer holds only the new certificate, as RFC 7030 says; the CA certificates come from cacerts.

Re-enrolling. simplereenroll replaces a certificate Lighthouse issued over EST through the same template, which must be unexpired and not revoked, and the CSR must keep its subject and names (RFC 7030 section 4.2.2). The client proves which certificate it renews in one of two ways: with that certificate as its TLS client certificate (no password needed), or with the credential's user name and password, in which case it renews the credential's latest certificate with the CSR's subject. The new certificate keeps the owners and tags of the one it replaces, and the audit log says renew ... replaces <thumbprint>.

Client certificates and IIS. Re-enrolling with the user name and password works everywhere. Re-enrolling with only a TLS client certificate needs the certificate to reach Lighthouse. Run as its own service (Kestrel), Lighthouse asks for one only on a simplereenroll without a password, by TLS renegotiation (TLS 1.2) or post-handshake authentication (TLS 1.3) over HTTP/1.1; OpenSSL-based clients such as curl and libest do this, Go-based clients such as estclient do not, so give those the credential as well (-user, -pass and -csr). Under IIS, the site must accept client certificates for /.well-known/est (SSL Settings, Client certificates: Accept), and with TLS 1.3 the binding must ask for them in the handshake (netsh http update sslcert ... clientcertnegotiation=enable). Lighthouse accepts any client certificate at the TLS layer and then checks it against what it issued itself, so the client's CA need not be trusted by Windows. If in doubt, give re-enrolling clients the credential.

Removing a credential stops its clients and the re-enrollments of what it issued at once; certificates already issued stay. Taking the team off the template, switching the template off or making it need approval stops new requests with a message saying so. Refusals are plain text with an HTTP status, as RFC 7030 says: 401 for a missing or wrong user name and password (with WWW-Authenticate: Basic), 403 when the template or the certificate being renewed no longer allows it, 400 for a request the template's rules refuse, and 502 when the CA refuses.

The EST addresses need no sign-in to the console, as every request carries an EST credential or the certificate it renews. With IIS, keep Anonymous Authentication on for the site; IIS's own Basic Authentication must stay off, so the user name and password reach Lighthouse. Lighthouse never asks for the TLS channel binding of RFC 7030 section 3.5 (the CSR's challengePassword set to tls-unique), as it is not available with TLS 1.3.

Facts

Facts are what you record about a certificate, such as its cost center, application or change ticket. Administrators add them under Templates & Policies, below the templates. Each fact has a name, a kind (text, number, date, yes or no, or a pick list), and optionally a hint, a default and a "required" switch. Once certificates have values for a fact its kind is fixed, since those values are kept in that kind's form. The one exception is a pick list, which can still become text: every choice is already valid text, so nothing is lost, and from then on any value is accepted.

Where it is asked. Ask on every template (on for a new fact) has Enroll ask for the fact whatever template is picked. Turned off, only the templates that tick it under Facts to ask for at enrollment ask for it. The Facts table's Asked on column says which, and a template's row lists every fact it asks for. People enrolling in the console, through POST /enroll or lh enroll, renewing through the API, or sending a request for approval are asked for both kinds. ACME, EST and SCEP cannot ask anyone, so they fill in only the defaults of the facts their template ticks. A required fact must have a value, or a default, wherever it is asked. When upgrading to this version, facts no template ticked (which were asked nowhere) are switched to every template; facts moved in from elsewhere come in switched off.

A text fact can also have a pattern: a regular expression (.NET syntax) the whole value must match, such as \d{4} for a four-digit cost center or (?i)CHG\d{7} for a change ticket in any case. A value that does not match is refused with the fact's hint, so write the hint for the person typing (for example "Four digits, from the finance system"). The default value must match too. Under the pattern, the Pattern tester checks sample values as you type, using the server's own check, and when you edit a fact it also lists values already on certificates that do not match. Those stay until someone changes them: renewing a certificate or editing its other facts keeps them. Patterns cannot use backreferences, lookahead, lookbehind or atomic groups, each value is checked within 250 ms, and a pattern that is too slow on long values is refused when you save it.

Start from an example fills in a ready-made pattern, a hint and sample values for the tester. Change example.com and corp.example.com to your own domains.

Example Pattern
Host name (common name or DNS name) (?i)[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+
Host name under one domain (?i)([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+example\.com
Host name or wildcard (?i)(\*\.)?([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,63}
IPv4 address ((25[0-5]\|2[0-4]\d\|1\d\d\|[1-9]?\d)\.){3}(25[0-5]\|2[0-4]\d\|1\d\d\|[1-9]?\d)
Email address [^@\s]+@([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?\.)+[A-Za-z]{2,63}
User principal name (UPN) in your domain (?i)[a-z0-9._-]+@corp\.example\.com
Active Directory distinguished name (?i)(CN\|OU)=[^,=]+(,(CN\|OU)=[^,=]+)*(,DC=[a-z0-9-]+)+
Serial number (hex) (?i)[0-9a-f]{2}(:?[0-9a-f]{2}){0,19}
SHA-256 thumbprint (?i)[0-9a-f]{64}
Cost center (four digits) \d{4}
Change ticket (?i)CHG\d{7}
Application ID APP-\d{3,6}

These check what people type into a fact. They do not check the certificate's own common name or SANs; which names a template may issue is set by its allowed domains.

A template asks for the facts ticked in its "Facts to ask for at enrollment" list. Enroll then shows a field for each, filled in with its default and tagged Required or Optional; a required fact must have a value, and every value is checked against its kind before anything goes to the CA. A request that waits for approval keeps its values, and the approver sees them on Requests. The issued certificate gets them.

Afterwards, whoever may change a certificate (its team's operators and administrators) can edit its facts in the details panel. Spotlights and search find certificates by fact (fact:Environment=Production), and Export CSV adds a column for each fact. Adding, changing and deleting facts, and every change of a certificate's values, is in the audit log (fact-add, fact-change, fact-delete, facts-set). Facts stay in Lighthouse: they are not written into the certificate or sent to the CA.

Certificates that are missing a fact. Certificates found on servers, or issued before a fact existed, have no value for it. The rule not fact:Cost center lists them in search, a Spotlight or GET /certificates?rule=.... To fill many in at once, script the API: PUT /certificates/{thumbprint}/facts sets one certificate's facts, and since it replaces all of them, send the certificate's current facts back with the new one. docs/api.md has a PowerShell example that reads a CSV of thumbprints and values.

Keeping private keys (optional)

Lighthouse keeps no keys until an administrator turns it on, but it is ready to: setup makes the certificate kept keys are sealed to. To hold keys:

  1. Make a recovery copy of the key-encryption certificate under Settings, Server, Application, Key encryption (below), and keep its passphrase, or its parts, off the server. Without the certificate, kept keys cannot be opened after the server is rebuilt.
  2. Under Settings, Server, Application, PFX downloads, choose how long, or on a template tick "Hold the private key of PFX enrollments" and choose a number of days, weeks, months or years, or indefinitely. Enroll's PFX tab then shows Save private key: the person enrolling chooses whether Lighthouse keeps the key. It is ticked to start with when the template holds keys of its own, and not ticked when the template follows PFX downloads. Not ticked, the key is never sent to the server.
  3. Optionally, let a group recover keys for other people: KEY_RECOVERY="CONTOSO\Lighthouse Key Recovery" when running setup.

The key-encryption certificate. When KEY_ENCRYPTION_CERT is empty, setup (new installs and upgrades) makes a self-signed certificate CN=Lighthouse key encryption with an RSA 3072 machine key in LocalMachine\My, gives the service account read access to its key, and writes its thumbprint into the settings. Running setup again reuses it. Its key is exportable so administrators can back it up; only SYSTEM, administrators and the service account can open it. To use a certificate of your own instead (RSA 3072 or larger, in LocalMachine\My), give its thumbprint as KEY_ENCRYPTION_CERT. Servers that share a database must share this certificate too: with GROUP_CERT, setup uses the group certificate when its key is RSA 3072 or more, and otherwise makes none, so give every server the same certificate with KEY_ENCRYPTION_CERT.

The recovery copy. Make recovery copy locks the certificate and its private key in a PFX (PBES2, AES-256, SHA-256) with a passphrase Lighthouse makes (six groups of five letters and digits, about 148 bits), shows it once and keeps nowhere, and stores only the locked copy in the database (the settings row key-encryption-recovery). The database and its backups then carry the copy while the passphrase stays with people, so a copy of the database alone still opens nothing. Keep the passphrase away from where database backups go. Instead of one passphrase, Lighthouse can split a random secret into 2 to 9 parts with Shamir's secret sharing, of which any 2 or more chosen open the copy (any 3 of 5, say); fewer tell nothing. Each part is text starting lh1- that carries how many are needed, its number, which copy it belongs to and a check, so a mistyped part, or one from another copy, is caught on its own. Check passphrase says whether a passphrase or parts open the copy without changing anything, Download locked copy gives the PFX file to keep off the server too, and Delete recovery copy removes it (older database backups keep theirs). A new copy replaces the old one. Lighthouse makes no copy of a key in hardware, or of a key made non-exportable. Each step is in the Keeper's Log as kek-recovery-copy, kek-recovery-check, kek-recovery-download or kek-recovery-delete.

Restoring it. On a rebuilt server, or a second one sharing the database, install Lighthouse first, then as an administrator on that server:

lh key-encryption restore --settings "%ProgramData%\Lighthouse\lighthouse.json"

It asks for the passphrase, or for each part in turn without showing it, checks they open the copy, puts the certificate back in LocalMachine\My as a machine key, gives the service account read on the key, writes its thumbprint as KEY_ENCRYPTION_CERT and records kek-restore. If setup made a new certificate meanwhile and keys were held under it, their data keys are re-wrapped to the restored certificate (key-reseal), so none are left behind. Then restart the service (Restart-Service Lighthouse) or recycle the app pool. --file <pfx> restores a downloaded copy instead of the database's.

The key in an HSM. To keep the key-encryption key in a hardware security module, install its client and key storage provider (KSP) on the server first, then give the provider's name as KEY_ENCRYPTION_PROVIDER:

msiexec /i Lighthouse-<version>-x64.msi KEY_ENCRYPTION_PROVIDER="SafeNet Key Storage Provider"

Setup makes CN=Lighthouse key encryption with an RSA 3072 machine key in that provider, which never lets it out. The HSM decides who may use the key, so give the service account access there (setup.log says so), and back it up with the HSM's own tools; Lighthouse makes no recovery copy and Settings shows where the key is. A server that already has a key-encryption certificate keeps it. To move to an HSM, make a new RSA 3072 certificate whose key is there, then run lh key-encryption use <thumbprint> as an administrator: it re-wraps every held key to the new certificate (the keys themselves are not opened), names it in the settings and records key-reseal. Restart the service afterwards. The same command moves to any other certificate. A certificate of your own whose key is in an HSM works the same, given as KEY_ENCRYPTION_CERT. Servers sharing a database must all reach the same HSM key.

PFX enrollments with that template seal the key in the browser to the key-encryption certificate before sending it. When the hold ends the key is destroyed (up to an hour later, when the hourly clean-up runs). A key held indefinitely is never destroyed on its own; it stays until an operator destroys it from the certificate's details or Key Storage. Changing a template's hold applies to keys held from then on; keys already held keep the date they were given. Upgrading from 0.16.0 or earlier turns a saved number of days into the same number of days. Members of the recovery group can recover a key as a new PFX from the certificate's details; they must give a reason, and each recovery is audited and announced through the alert channels. Operators can destroy a kept key early.

A second person for key recovery. With Recovering a kept key needs a second person on under People & Access, Safeguards (off by default; only an administrator changes it, and each change is announced), Recover puts a request on Requests instead of downloading the key. Someone else who may recover that key approves it there, and then the person who asked recovers the key once, from the certificate's details, within the hours Safeguards gives (1 to 72, default 4). A request nobody decides is refused after 7 days.

Two or more servers

Several Lighthouse servers can share one database, behind one name such as lighthouse.example.test, so a server can be patched or fail without Lighthouse going down. Everything Lighthouse knows is in the database, so any server can answer any request. Four things need care, and Lighthouse handles each through the database rather than with another service to install:

  • Jobs run once. Every server that may run jobs (RUN_JOBS=1) asks for the jobs lease every 20 seconds. One holds it and runs the scheduled jobs, Run now and kept-key retention; the others stand by. If the holder stops or loses the database, the lease runs out after 90 seconds and another server takes over, first marking jobs the old one left Running as failed. A clean stop hands the lease over at once. Run now is recorded on the job, so it works from whichever server the browser reached. Jobs still run one at a time.
  • Secrets are shared. Without a group certificate each server keeps its own secrets with DPAPI, which no other machine can open. With GROUP_CERT, secrets live in the database, sealed to that certificate: set the SMTP password on one server and every server uses it.
  • Every server shows the same certificates. Each server keeps the certificate list in memory, so pages stay quick with hundreds of thousands of certificates. Every save marks what it changed in the database, and before each page a server checks the marks and reads again only the certificates that changed, so a tag set through one server shows on the next page from any other. A server reads the whole list as it starts: about 6 seconds for 100,000 certificates and 40 for 500,000. Allow about 2 GB of memory for 100,000 certificates and 6 GB for 500,000 on each server (docs/load-testing.md).
  • Sign-in sessions work on every server. With GROUP_CERT, the keys that protect sign-in cookies (OpenID Connect) are kept in the database, encrypted to the same certificate, so a cookie from one server is accepted by the others. Windows sign-in needs nothing extra: each request signs in on the server it reaches.

The group certificate is an ordinary RSA certificate (2048 bits or more) with the same private key on every server. Request it once with an exportable key, for example from your issuing CA with a template that allows key export, import the PFX into LocalMachine\My on each server, and delete the PFX. It is only used to seal and open data, never for TLS, so it needs no particular name or usage. Keep a copy of the PFX somewhere safe: without the key, shared secrets cannot be opened and must be set again.

Adding a second server:

  1. Let the new computer use the service account: add it to the gMSA's PrincipalsAllowedToRetrieveManagedPassword (Set-ADServiceAccount gmsa-lighthouse -PrincipalsAllowedToRetrieveManagedPassword ... with every server's computer account), then run Install-ADServiceAccount gmsa-lighthouse on it. The database login is the gMSA, so SQL Server needs no change, only a network path from the new server. A domain user account works the same way with its password given to each server; with the built-in account, each server's computer account needs its own SQL login and CA rights.
  2. Give the new server its own HTTPS certificate for the shared name (its key can stay non-exportable) and import the group certificate.
  3. On the first server, add the group certificate and share the secrets it already has:
    msiexec /i Lighthouse-<version>-x64.msi GROUP_CERT=<thumbprint>
    lh secret share --settings "%ProgramData%\Lighthouse\lighthouse.json"
    
  4. Install on the new server with the same settings and MIGRATE_DB=0: the same SIGNIN, groups, GROUP_CERT, CONNECTION and SERVICEACCOUNT, its own HTTPS_CERT, and URL=https://<shared name>. With sign-in providers, copy their part of lighthouse.json too; their client secrets are already shared.
  5. If keys are held and the key-encryption certificate is not the group certificate, give the new server the same one: lh key-encryption restore from the recovery copy (see Keeping private keys).
  6. Point the load balancer at both servers (below), then check Settings, Application, Servers: both are listed as up, and one says running jobs.

What a jobs server needs. Any server that may run jobs must be able to do every job: reach the scanned networks, CAs and install targets, and have the CA client certificates the jobs name (CLIENT_CERTS, the same thumbprint on each, so their keys must be exportable once) and the SSH key and known_hosts in %ProgramData%\Lighthouse\ssh. Until that is true, install the new server with RUN_JOBS=0: it serves the console and API, and Run now still works there, while the first server keeps the jobs.

The load balancer. Use any load balancer that passes TCP through (layer 4), such as F5 BIG-IP, HAProxy, NetScaler, or a firewall's own load balancer, with source-address persistence, so the HTTPS certificate and Windows sign-in stay on the Lighthouse servers. NTLM signs in a connection, not a request, so a balancer that ends TLS itself breaks NTLM unless it keeps each client connection on one server. For health checks, use GET /health on each server: it needs no sign-in and returns 200 with {"status":"ok","server":"LH01","runningJobs":true} while the server can reach the database, and 503 when it cannot. It shows nothing from the inventory. For Kerberos, register HTTP/<shared name> once on the gMSA (see Running in IIS); because every server runs as the gMSA, each can accept the same tickets. Windows Network Load Balancing also works, without a separate box, but it needs the servers on one subnet and knows nothing about /health.

Taking a server out. Stop it, or drain it in the load balancer and stop it. A server that has stopped stays in the Servers list as not seen; Forget removes it.

The database is now the one shared part. For it to survive a server failure too, use a SQL Server Always On availability group or failover cluster and point CONNECTION at its listener; Lighthouse needs nothing else for that.

Kubernetes clusters

Ingresses and Gateways serve the certificate in a kubernetes.io/tls secret (tls.crt and tls.key). Lighthouse keeps those current two ways, for two kinds of secret:

  • cert-manager, if the cluster runs it: point it at Lighthouse ACME (below). cert-manager makes the key, keeps the secret and renews by itself, and the certificates come into the inventory owned by the ACME key's team.
  • A Kubernetes Installation, for a secret nobody renews (made with kubectl create secret tls, or in a cluster without cert-manager). It is an Installation like the others: the place, and the certificate it keeps through a template, with a daily check, auto-renew, Run now, Install a certificate, Enroll's Install it into an Installation, Plan, Drills and CA migration.

A Kubernetes Installation

It belongs to Outposts (Settings, Features); there is no switch of its own. It runs only from an Outpost inside the cluster. The Outpost's pod makes the key and writes it straight into the secret, as its own service account; Lighthouse sees the CSR and the certificate, never the key, and never holds a credential for the cluster. Saving one without an Outpost is refused, and so is running one.

Field What it is
Cluster name The name Lighthouse shows and records the place under, such as prod-east. The Outpost decides which cluster it is: the one it runs in.
Namespace, TLS secret Where the secret is. A secret that is not there yet is made, as kubernetes.io/tls.
Check it is served at, port, SNI Optional: the Ingress's or Gateway's host. After the install the Outpost checks it serves the new certificate, three times 10 seconds apart, while the controller picks up the change.
Template, key type, renew before, subject, DNS names As for the other Installations. With no subject, the current certificate's subject and names are copied.

Each run, from the Outpost:

  1. Checks its rights. A SelfSubjectAccessReview for get, create, update and delete on secrets in the namespace. If any is missing the run stops before a key is made, naming the missing rights and installer/kubernetes/namespace-access.yaml.
  2. Reads the secret and records its certificate, and lists the Ingresses (spec.tls[].secretName) and Gateways (spec.listeners[].tls.certificateRefs) in the namespace that use it, for the run's result ("Served by Ingress www (www.example.com); Gateway public (example.com)"). A cluster without the Gateway API, or a right to list them, just leaves them out.
  3. Leaves cert-manager's secrets alone. A secret with the cert-manager.io/certificate-name annotation is read but never changed; the run says to point cert-manager at Lighthouse ACME instead.
  4. Makes the key in the Outpost's process and keeps it in <secret>-lighthouse-pending (Opaque, labelled app.kubernetes.io/managed-by: ironshore-lighthouse, with the request's id) while the template's CA issues. Only the CSR goes to Lighthouse.
  5. Installs. The certificate must match the pending key and request. The current tls.crt and tls.key are copied to <secret>-lighthouse-previous, then the secret gets the new tls.crt (the certificate and its chain, without the root) and tls.key in one update, guarded by its resourceVersion. Its other keys (such as ca.crt), labels and annotations stay as they were; Lighthouse adds lighthouse.ironshore/thumbprint. The pending secret is removed.
  6. Proves it. Reads the secret back, and checks the served address if one is given. If the new certificate is not there or not served, the previous tls.crt and tls.key are put back and the run fails.

Ingress controllers (ingress-nginx, Traefik, HAProxy and others) and Gateway implementations watch the secret and pick up the change by themselves; nothing is restarted. The place is recorded as //<cluster>/kubernetes/<namespace>/<secret>, so Discovery, Runway and rules see it like a file. Kept keys never go to a cluster: Install a certificate always has a new key made there. Drills and CA migrations reach the secret through the same Outpost, read-only in a dry run.

The Outpost in the cluster

The Outpost runs in the cluster as a Deployment of one pod. Everything is in installer/kubernetes/:

  • Dockerfile builds the image from the repository root: the Outpost published self-contained for linux-x64 on runtime-deps:10.0, running as user 10001. Build it and push it to your own registry: docker build -f installer/kubernetes/Dockerfile -t registry.example.com/ironshore/lighthouse-outpost:<version> .
  • outpost.yaml makes the namespace lighthouse, the service account lighthouse-outpost, a Role that lets it read and update only its own key secret, a Secret with the join code, and the Deployment: one replica with the Recreate strategy (one pod is one Outpost computer), non-root, read-only root filesystem, all capabilities dropped, and no ports.
  • namespace-access.yaml is the Role and RoleBinding for one namespace: get, create, update and delete on secrets, and list on Ingresses and Gateways. Apply it once for each namespace, with its name: sed 's/namespace: shop/namespace: web/' namespace-access.yaml | kubectl apply -f -. Nothing is given cluster-wide.

To set it up: add the Outpost on Settings, Outposts as usual, put its join code and Lighthouse's address in outpost.yaml, kubectl apply -f outpost.yaml, and apply namespace-access.yaml for each namespace. The pod (lh-outpost kubernetes) reads its settings from its environment, not outpost.json: LIGHTHOUSE_SERVER, LIGHTHOUSE_JOIN_CODE, and optionally LIGHTHOUSE_ALLOW (comma-separated), LIGHTHOUSE_PROXY, LIGHTHOUSE_CONCURRENCY and LIGHTHOUSE_KEY_SECRET. It joins once and keeps its key in the secret lighthouse-outpost-key in its own namespace, instead of DPAPI; a new or moved pod reads it there and carries on without joining again, so delete the join code secret once it shows Online. It reaches the cluster's API as its service account, with the token and CA the cluster mounts in the pod (the token is read again for each call, as Kubernetes rotates it).

Two pods for redundancy. Press Add a computer on the Outpost, and apply a copy of the Deployment with another name, the new join code, and LIGHTHOUSE_KEY_SECRET set to a secret of its own (such as lighthouse-outpost-key-2), adding that name to the resourceNames of the Role in outpost.yaml. Both pods share the work; since the new key waits in a secret in the cluster, one pod can make the request and the other install it. Every computer of an Outpost that keeps Kubernetes secrets must be in that same cluster, because either may take the work. Tried in the cloud with two pods of one Outpost: each run's steps went to both, and every install was verified.

The same Outpost can also scan, reach appliances and CAs from inside the cluster's network, like any Outpost, within LIGHTHOUSE_ALLOW. Its Kubernetes work is bounded by its service account's Roles, not by the allow list, because it only ever reaches its own cluster's API.

If the cluster refuses the service account's token (401) the run stops at that call. A missing right (403) names it and the Role to apply.

cert-manager through Lighthouse ACME

cert-manager is an ACME client like any other. Make an ACME key for the team and a template without approval, put the HMAC key in a Secret in cert-manager's namespace, and add an issuer:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: lighthouse
spec:
  acme:
    server: https://lighthouse.example.com/acme/3/directory
    email: [email protected]
    privateKeySecretRef:
      name: lighthouse-acme-account        # cert-manager makes the ACME account key here
    externalAccountBinding:
      keyID: <key id>
      keySecretRef:
        name: lighthouse-acme-key          # kubectl -n cert-manager create secret generic lighthouse-acme-key --from-literal=hmac=<HMAC key>
        key: hmac
    # caBundle: <base64 PEM of your private CA>, when Lighthouse's own HTTPS certificate is from it
    solvers:
      - http01:
          ingress:
            ingressClassName: nginx

Then a Certificate (or the cert-manager.io/cluster-issuer: lighthouse annotation on an Ingress) asks for one. Lighthouse proves each name itself: for http01 the Lighthouse server fetches http://<name>/.well-known/acme-challenge/... on port 80, which cert-manager's solver Ingress answers; for dns01 (which wildcards need) it looks up the TXT record. Names and key types must be ones the template takes. The example issuer and a Certificate were checked against cert-manager 1.16's own resource schemas. How-to: Point cert-manager at Lighthouse ACME.

Outposts

Lighthouse reaches most servers itself, across forest trusts too. An Outpost does that connecting for it, for two reasons:

  • Reach. Networks Lighthouse cannot open connections into: a DMZ whose firewall only lets traffic out, a forest with no trust, a site with no route back.
  • Load. Scans, Installations and CAs Lighthouse could reach itself can still be set to run from an Outpost, so the connecting and waiting (TLS checks, scans in pieces of 512 addresses and ports, SSH, PowerShell and appliance install steps, calls to the CA) run on the Outpost's computers instead of the Lighthouse server. Lighthouse still decides, checks and records; for more of that, add a Lighthouse server sharing the database. Several computers on one Outpost share its work.

It is a small Windows service (lh-outpost.exe, service LighthouseOutpost) on a computer in the network it serves, or a pod in a Kubernetes cluster (see The Outpost in the cluster). It calls Lighthouse over HTTPS, never listens on a port and keeps no database. It is the one optional agent; everything else stays in the one server.

What runs where. The job still runs on the Lighthouse server that runs jobs: it decides whether a certificate is due, asks the CA through the template, checks the certificate and records what it found in the inventory and the Keeper's Log. Only the connections into that network go through the Outpost: TLS checks, scans (in pieces of 512 addresses and ports), the SSH, PowerShell and appliance API steps of an Installation, and, for a CA reached from it, reading, issuing and revoking. The CA checks of a Replace-All Drill still come from the Lighthouse server. Private keys are made on the server the certificate is for, as always; only CSRs, certificates and the steps' results pass through.

Setting one up:

  1. Turn on Outposts under Settings, Features. They are in every license (Standard and up), because Installations renew through them.
  2. On Settings, Outposts, choose Add Outpost and name it, such as DMZ west. Lighthouse shows a setup line with a join code (lhj_...) that works once, within 24 hours. Lighthouse keeps only its hash.
  3. Download the Outpost on a Windows computer in that network. The Outposts page and the join code dialog link the Outpost MSI for the server's own version at https://ironshoretech.com/downloads/lighthouse/<version>/, with SHA256SUMS.txt to check it. Double-click the MSI: its Join Lighthouse screen asks for the Lighthouse address and the join code, both shown by the join code dialog, and optionally the allow list and a proxy. Or, from an elevated prompt in that folder, run the line it shows:
    msiexec /i Lighthouse-Outpost-<version>-x64.msi SERVER=https://lighthouse.example.com JOIN_CODE=lhj_...
    
    Add /qn for a silent install, which needs SERVER and JOIN_CODE on the line. The MSI installs the service, joins and starts it. It needs nothing else first: it carries its own .NET, and needs no IIS and no Hosting Bundle. The computer must reach Lighthouse's address on 443 and trust its HTTPS certificate. The Outpost has its own MSI only: setup.exe installs only the server and refuses a computer that runs the Outpost alone, and the Outpost MSI refuses a computer that runs the Lighthouse server. Upgrade an Outpost with the newer Outpost MSI; it keeps its key, so it needs no join code and skips the join screen. When an Outpost needs upgrading, and when it can stay as it is, is in Upgrading Lighthouse and Outposts.
  4. The Outpost shows Online. Pick it under Run from on a scan in Discovery, or on an IIS, Linux, network appliance or Kubernetes Installation, and under Reach it from on an ADCS, EJBCA or Vault CA in that network (Settings, CAs). Only administrators choose either; a team operator's change keeps what is set.

Other Outpost MSI properties: ALLOW limits where the Outpost may connect (below), PROXY=http://proxy.example.com:8080 reaches Lighthouse through an HTTP proxy, and SERVICEACCOUNT=EXAMPLE\gmsa-outpost$ runs it as a gMSA instead of NT SERVICE\LighthouseOutpost. Use a gMSA, or give the computer account rights, for PowerShell over WinRM to other Windows servers, or give each Windows Installation a Sign in as account whose password is an Outpost secret, and the Outpost's own account needs no rights on them.

Its own sign-in. The Outpost signs in to Linux servers with its own SSH key and known hosts in %ProgramData%\Lighthouse\Outpost\ssh (same files as the server's ssh folder: the private key and known_hosts), and to Windows servers as its service account. The credentials of that network stay in it.

Only what is built in. An Outpost runs only the TLS probe, the named install scripts built into it (SSH: read the installed certificate, make a request, install, restore; PowerShell: check IIS, make a request, install the certificate, bind IIS), the same four steps against an appliance's management API, and issuing, revoking and reading at a CA. Lighthouse cannot send it anything else to run.

Network appliances. An F5 BIG-IP, Palo Alto or Citrix ADC Installation with Run from set has the Outpost sign in to the appliance's management API and do each step there. The appliance still makes its own key. The allow list is checked against the management address.

CAs in that network. An ADCS, EJBCA or Vault CA with Reach it from set is read through the Outpost (in pages of 200, the Outpost keeping its place for up to 10 minutes between pages), and every template that uses it issues and revokes through the Outpost: Lighthouse checks the request first and the certificate again when it comes back. The Outpost reaches ADCS as its own Windows account (give it Request Certificates, and Read on the CA to sync), EJBCA with the client certificate whose thumbprint the CA has, in the Outpost computer's LocalMachine\My store, and Vault with the credential under the CA's secret name, set on the Outpost. Test connection connects from the Outpost. DigiCert and the public CAs stay reached from the server.

Its own secrets. Passwords and tokens for the Outpost's work never go to Lighthouse. Lighthouse sends only a secret's name (an appliance Installation's password secret, a Vault CA's credential secret), and the Outpost reads it from its own secrets folder, sealed to the computer with DPAPI:

lh-outpost secret set f5-admin        (asks for the value without showing it, or reads it piped in)
lh-outpost secret list                (names only)
lh-outpost secret remove f5-admin

Work that needs a secret the Outpost does not have fails, naming it, the computer, and the two ways to set it. Restarting the service is not needed.

Secrets typed in the console. On the Outposts page, Secrets… lists the secret names each computer has (never the values) and lets an administrator type one, with the name the Installation or CA gives. The browser seals it to each computer before sending it:

  • When the Outpost service starts, each computer makes an RSA 3072 sealing key, kept in seal.key next to its key, sealed to the computer with DPAPI. Lighthouse gets only the public half, when the computer joins and each time it starts.
  • The browser seals the secret with a new AES-256-GCM key, with iRonShore Lighthouse Outpost secret <name> as additional data, and wraps that key with RSA-OAEP (SHA-256) for each computer's public key. A sealed value cannot be opened by Lighthouse, by another computer, or under another name.
  • Lighthouse keeps the sealed value only until the computer takes it. A waiting secret ends the computer's wait for work at once (it is named in the X-Lighthouse-Secrets header), so it arrives within a second or so while the computer is online. The computer opens it, keeps it in its secrets folder as lh-outpost secret set would, and Lighthouse deletes the sealed value.
  • Each computer also reports the names of its secrets every 5 minutes, including ones set with lh-outpost secret set, so the page stays right.
  • Remove next to a name removes it from every computer on its next call.
  • A computer installed again makes a new sealing key; what was sealed to its old one and not yet taken is dropped. A computer added later has none of the typed secrets: type them again so they reach it too.
  • Computers running an older Outpost have no sealing key. Typed secrets skip them, and the page says so; set secrets there with lh-outpost secret set.

Typing a secret is recorded in the audit log with its name and the computers, never the value. The page that seals the value comes from Lighthouse, so this protects the secret from the Lighthouse database, backups, logs and anyone with access to them, but it is only as trustworthy as the Lighthouse server that serves the page. Where even that is not acceptable, use lh-outpost secret set on the computer.

The allow list. The Outpost's own administrators decide where it may connect, in %ProgramData%\Lighthouse\Outpost\outpost.json:

{ "server": "https://lighthouse.example.com", "allow": ["10.20.0.0/16", "192.168.5.5", "*.dmz.example.com"], "concurrency": 4 }

Entries are networks, addresses and host names (*. for a whole domain). A name must resolve only to allowed addresses or be listed itself. Work for anywhere else fails with the reason. An empty list allows anywhere the computer can reach. Lighthouse cannot change this file; restart the service after editing it.

Its key. Joining swaps the join code for the computer's own key (lho_...), kept in outpost.key, sealed to that computer with DPAPI. Setup limits the folder to SYSTEM, Administrators and the service's account. Lighthouse keeps only hashes of both. Add a computer on the Outposts page (New join code before any has joined) makes a new join code; a computer's old key works until the code is used on it. Turning an Outpost off or removing it stops every key of it at once. An Outpost that scans or Installations still run from cannot be removed; move them first.

Offline. A running Outpost calls in at least every 30 seconds. One that has not called in for 2 minutes shows Offline, and jobs that need it fail at once saying so, rather than waiting. On the Outpost's computer, lh-outpost check says whether Lighthouse can be reached and takes its key. The service logs to the Windows Application log (source LighthouseOutpost), once each time its state changes.

How it talks to Lighthouse. Under /outpost/v1, never /api/v1, with its key in the X-Lighthouse-Outpost header (never an API token):

Call What it does
POST /outpost/v1/join {"code", "machine", "version", "sealKey"} swaps a join code for {"id", "name", "key"}. A bad code is 401 and counts toward the bad-token limit for the address.
GET /outpost/v1/hello Which Outpost the key belongs to; takes no work (lh-outpost check).
GET /outpost/v1/work?wait=25 The next piece of work, holding the request open up to 30 seconds while there is none (204).
POST /outpost/v1/work/{id} {"ok", "result", "error"} for that piece; 410 when Lighthouse stopped waiting for it.
POST /outpost/v1/secrets {"sealKey", "names", "taken"}: the computer's sealing key, the names of its secrets and the ids of waiting secrets it has just taken; answers {"waiting": [{"id", "name", "sealed"}]} (sealed null for a removal).

While Outposts are off these answer 404. An unknown, replaced or turned-off key is 401, counted like a bad API token. Work waits in the database, so any Lighthouse server can hand it out and take the answer, and the job's own server picks it up. With several servers behind a load balancer, the Outpost may reach any of them.

More than one computer. Add one Outpost per network, and install it on two or more computers there for redundancy. On the Outposts page, Add a computer makes a join code for the next one. Each computer has its own key. Whichever is free takes the next piece of work, so one that is stopped or being patched takes none and the others carry on. The Outpost is online while any of its computers is. Scans, Installations and CAs keep naming the Outpost, not a computer. A join code used on a computer the Outpost already has (same computer name) replaces that computer's key, which is how you install one again. Remove next to a computer stops its key and leaves the others working. Give every computer the same allow list and the same secrets (typed once under Secrets…, which reaches every computer, or lh-outpost secret set on each). Work a computer took is finished by that computer: if it stops halfway, that piece fails when its time is up and the job's next run uses the others. The next page of a CA reading always goes back to the computer that started it.

Browser protection and request limits

Every page carries a strict Content Security Policy (scripts and styles only from Lighthouse, no framing), X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer and Cache-Control: no-store. Over HTTPS it also sends Strict-Transport-Security: max-age=31536000, so a browser that has visited once refuses plain http to that name for a year. It is set for the name only, not its subdomains.

The addresses that need no sign-in answer each client address only so often, before sign-in or the database is touched:

What Limit per client address Then
ACME, SCEP and EST requests together 300 a minute (an ACME certificate takes about ten) HTTP 429 with Retry-After; ACME clients get the rateLimited problem and wait
/mcp 120 a minute HTTP 429 with Retry-After
Unknown, removed or expired API tokens 20 in 5 minutes Every API token from that address gets HTTP 429, without a lookup, until the 5 minutes pass

Each server counts on its own, in memory, and the console and signed-in API are not limited. The first refusal for an address in a few minutes is a warning in the Server Log (part Web server). The limits count the address the connection comes from, so with a load balancer pass TCP through (see Two or more servers) rather than ending TLS on it, or every client counts as the balancer.

Behind a firewall port forward

Forward one outside address and port to the server's HTTPS port only. Put that outside address (or the name that resolves to it) in the HTTPS certificate's SAN, and browse to it. Sign-in works the same way; with NTLM nothing else is needed.

Upgrading Lighthouse and Outposts

Lighthouse has two parts to install, and they are upgraded separately:

  • The server: Lighthouse-Setup-<version>-x64.exe or Lighthouse-<version>-x64.msi, on one or more servers sharing one SQL Server database.
  • Outposts: Lighthouse-Outpost-<version>-x64.msi on Windows computers, or the Outpost image as a pod in a Kubernetes cluster.

The server always goes first. An Outpost follows when it needs to. This is the order other enterprise products use as well. An Outpost that is behind the server keeps doing everything it already did. An Outpost is never newer than the server: the Outposts page links the Outpost MSI for the server's own version.

Kinds of release

The version number says how big a change a release is: major.feature.patch.

Kind Example What changes Database Outposts Going back
Patch 1.4.0 to 1.4.1 Fixes only No schema change Only if the notes say an Outpost fix is in it Install the previous version again
Feature release 1.4 to 1.5 New features and fixes May change the schema Only to use a new feature that runs on an Outpost (below) Restore the database backup, then install the previous version
Major release 1.x to 2.0 May change how Outposts talk to the server, or drop old settings May change the schema Within that major release (below) Restore the database backup, then install the previous version

Until 1.0, every build is a feature release (0.36.0, 0.37.0 and so on), and only the newest version gets fixes. Treat every feature release as one that may change the database, and back the database up first, even when a version turns out to have no schema change (0.36.0 had none, 0.35.0 had one).

What a major release promises Outposts. A change that older Outposts could not follow gets a new address for Outposts to call (/outpost/v2 beside today's /outpost/v1). The server keeps answering the old one for the whole of that major release, so Outposts can be upgraded in the weeks after the server, not in the same night. Up to now, every Outpost change has been an addition that older Outposts politely turn down (below), so nothing has needed this yet.

When the server needs upgrading

  • To get a feature or a fix you want. Nothing makes you upgrade otherwise; a server keeps running the version it has.
  • When a version fixes a security problem: upgrade soon, as with any server.
  • When an Outpost you just set up needs a newer server. This happens only if the Outpost MSI came from a newer folder than the server's own version. Use the link on the Outposts page, which always matches the server.

When an Outpost needs upgrading

An Outpost needs upgrading when:

  1. You start using something that runs on the Outpost and is newer than it. Each kind of work an Outpost does is built into it, and an older one turns down work it does not know: the job fails with This Outpost does not know … work; update it to the version of the Lighthouse server, and nothing is done on the target. Everything it already did carries on.

    To use this from an Outpost The Outpost needs at least
    Scans, and IIS and Linux Installations signing in as the Outpost's own account 0.30.0 (any Outpost)
    Network appliance Installations, ADCS, EJBCA and Vault CAs reached from it, secrets typed in the console, the Outpost in a Kubernetes cluster 0.31.0
    Windows Installations with their own Sign in as account 0.33.0
    Linux Installations signing in with a Lighthouse certificate or a stored key, checking a Linux server's host keys from the Outpost, password manager references 0.36.0
    Any Windows Installation run from the Outpost (an older one starts its PowerShell scripts in a way that runs nothing), and Adopt a server 0.37.0

    An Outpost older than 0.31.0 has no sealing key, so secrets typed in the console skip it, and the Secrets… dialog says so.

  2. The notes for a version say Outposts need it: a fix in the Outpost itself, or a major release that changes how Outposts talk to the server.

  3. It is more than one major release behind the server. Within one major release, an older Outpost keeps working.

Otherwise, leave it. An Outpost in a DMZ that only scans does not need touching at every server upgrade. Settings, Outposts shows each computer's version next to its name (DMZ-OP1 · 0.34.0), and lh-outpost --version says it on the computer.

Before you upgrade

  1. Read what changed in the new version, and decide whether any Outposts need it (above).
  2. Pick a quiet time. The service stops for a minute or two (longer if the database changes on a large inventory: up to a few minutes at 500,000 certificates). Jobs running at that moment stop and run again on their next schedule. Keep clear of change windows where renewals are planned to install (see Change windows and planned installs). Tell people with Settings, Application, Message for everyone signed in.
  3. Back up the database, and check the backup. Use a copy-only backup so your regular backup chain is not affected:
    BACKUP DATABASE [Lighthouse] TO DISK = N'D:\Backup\Lighthouse-before-0.37.0.bak' WITH COPY_ONLY, CHECKSUM, COMPRESSION;
    RESTORE VERIFYONLY FROM DISK = N'D:\Backup\Lighthouse-before-0.37.0.bak' WITH CHECKSUM;
    
    This backup is your way back if the upgrade changes the database (see Going back).
  4. Keep the recovery pieces that live outside the database: a copy of %ProgramData%\Lighthouse\lighthouse.json, the group certificate's PFX if you use one, and a recovery copy of the key-encryption certificate if Lighthouse holds private keys (see Keeping private keys). An upgrade keeps all of these, but they are what you need if a server has to be built again.
  5. Have the previous version's installers to hand. Each version's folder at https://ironshoretech.com/downloads/lighthouse/<version>/ never changes, so the version you run now stays downloadable.
  6. Check who is installing. The installer changes the database schema as the person running it, who needs rights to change it (such as db_owner). If your DBAs make schema changes themselves, install with MIGRATE_DB=0 and have them run lh db migrate --settings "%ProgramData%\Lighthouse\lighthouse.json".

Upgrading the server

One server. Run the new setup.exe or MSI with no properties: double-click it, or silently:

Lighthouse-Setup-<version>-x64.exe /quiet
msiexec /i Lighthouse-<version>-x64.msi /qn

It stops Lighthouse, replaces the program, changes the database schema if the version needs it, and starts Lighthouse again. It keeps the settings, the service account, how Lighthouse runs (IIS or service), the main sign-in, the secrets, the key-encryption certificate and the license, and it skips the setup screens. With a domain user or local account, Windows needs its password again: a double-click upgrade asks for it on one screen, and a silent one needs SERVICEPASSWORD. If you deploy the prerequisites yourself (Intune, SCCM, Group Policy), use the plain MSI the same way.

Two or more servers. Every server sharing a database must run the same version. A server does not check the database's schema when it starts, so a server left on the old version, running against a database the new version has changed, can fail in ways it cannot explain. So:

  1. Drain all but one server in the load balancer and stop them (stop the Lighthouse service, or the IIS site and app pool). The one left takes over running jobs: at once after a clean stop, or within 90 seconds.
  2. Upgrade the one still running. It changes the schema.
  3. Upgrade each of the others with MIGRATE_DB=0 (the schema is already done), and put it back in the load balancer.
  4. Check Settings, Application, Servers: every server shows the new version, and one says running jobs.

Lighthouse is down for the few minutes of step 2. With no database change, the others could be upgraded one at a time while the rest serve. Since that cannot be known ahead of the notes, the steps above are the safe default.

Afterwards, check:

  • The console opens, and the version at the foot of the page (and on Settings, Application, Servers) is the new one.
  • GET /health answers 200 on each server.
  • lh db migrate --settings "%ProgramData%\Lighthouse\lighthouse.json" says there is nothing to do.
  • The Server Log (Settings, Logs) has no new errors.
  • Settings, Outposts: every Outpost is Online again within about two minutes. An Outpost that cannot reach Lighthouse waits 5 seconds and tries again, doubling the wait up to 2 minutes, so it comes back by itself.

Upgrading Outposts

Upgrade Outposts after the server, from the link on the Outposts page, which is the Outpost MSI of the server's own version.

On Windows. On each Outpost computer, double-click the new Outpost MSI, or from an elevated prompt:

msiexec /i Lighthouse-Outpost-<version>-x64.msi /qn

No join code, SERVER or other properties are needed. It keeps the computer's key, its sealing key, its secrets, outpost.json (the allow list, proxy and concurrency) and its service account, skips the Join Lighthouse screen and starts the service again. Outposts installed before 0.34.0 (when they came from setup.exe) upgrade the same way. The MSI is silent-friendly, so Intune or SCCM can push it.

An Outpost on several computers. Upgrade one computer at a time. While one is being upgraded, the others take its share of the work. Work the upgraded computer had taken fails when its time is up, and the job's next run uses the others.

In a Kubernetes cluster. Build and push the image for the new version (see The Outpost in the cluster), then change the image of the Deployment:

kubectl -n lighthouse set image deployment/lighthouse-outpost outpost=registry.example.com/ironshore/lighthouse-outpost:<version>

Or change the tag in outpost.yaml and apply it again. The Deployment uses the Recreate strategy, so the old pod stops before the new one starts. The key stays in its secret, so the new pod carries on without joining again. With a second pod, upgrade its Deployment the same way afterwards.

Afterwards, check that Settings, Outposts shows the new version next to each computer and that it is Online. On the computer, lh-outpost check says whether Lighthouse can be reached and takes its key.

Going back

An Outpost can always go back, because an older Outpost works with a newer server. The Outpost MSI will not install over a newer one, so uninstall it first (Apps, or msiexec /x), which keeps its key, secrets and settings, then install the older Outpost MSI. In Kubernetes: kubectl -n lighthouse rollout undo deployment/lighthouse-outpost.

The server, when the version did not change the database. Uninstall Lighthouse (the settings, secrets and database are kept), then install the previous version's setup.exe or MSI. It finds the settings and starts as before.

The server, when the version changed the database. The previous version does not know the new schema, and Lighthouse does not undo schema changes on its own. Going back means restoring the backup taken before the upgrade:

  1. Stop Lighthouse on every server.
  2. Restore the database from that backup:
    RESTORE DATABASE [Lighthouse] FROM DISK = N'D:\Backup\Lighthouse-before-0.37.0.bak' WITH REPLACE, CHECKSUM;
    
    If the server's login or the service account's database user was added after the backup, run lh db migrate --grant <account> once more afterwards.
  3. Uninstall the new version on every server and install the previous one, the first with MIGRATE_DB=1 and the others with MIGRATE_DB=0.
  4. Roll back any Outposts that are now newer than the server.

What happened between the backup and the restore is gone from Lighthouse: certificates found or issued, approvals, changes to settings and Keeper's Log entries. Most of it returns by itself: the next CA syncs bring back certificates the CAs issued, and the next scans find what was installed. Keeper's Log entries forwarded to your log system (see Log forwarding) stay there. So go back soon after an upgrade, not days later. A patch that only fixes a problem is usually the better way forward.

Why restore rather than undo. Each schema change could, in principle, be undone in place, but an undo that runs only on the bad day is the one least tested, and some changes cannot be undone without losing data. A checked backup taken minutes before is the one way back that always works. This is also how other products that keep their data in SQL Server go back.

On your own PC

Without sign-in, lh serve --open listens on http://localhost:5080 only, and you are an operator: anyone who can run lh on the machine can already scan and sync. Requests for any other host name are refused, so web pages cannot reach it through DNS tricks.