6. TLS and certificates
This guide describes Lawliet 1.5.13.s (the current release). Screens, commands and procedures may change in later versions; the usage guide is updated with each release. Check the version your server runs under Settings > Platform or with ./lawliet version, and see the Releases page for what changed.
How the server's certificate works, how to check it, replace it, re-issue it with agents online, and how to turn encryption off and on.
| You need | |
|---|---|
| Who | Root (or sudo) on the Lawliet server for every change |
| Console | The Certificate card under Settings > Platform (permission Manage settings) shows the state. It is read-only: changes are made on the server |
| Four-eyes | Not applicable (CLI) |
6.1 How it works#
- In HTTPS mode, nginx serves the certificate and key in
infra/docker/ssl/fullchain.pemandprivkey.pem. No other container reads the private key. - A certificate must name every address it is reached by. An IP address is matched only by an IP entry in the certificate, a name only by a DNS entry. A wildcard covers exactly one leftmost label and never an IP address. Otherwise the agent installer fails with curl error 60 (no alternative certificate subject name matches target ipv4 address).
- Agents verify the server. With a certificate from a CA the hosts already trust, nothing else is needed. With a self-signed certificate, each agent was given that exact certificate file when it was installed, and trusts only that file.
- When the certificate changes, agents 2.1.9.s and later can be handed the new one over their existing connection before the server switches, so they stay online. Older agents, and agents that are offline during the switch, must be given the new certificate by hand (6.6). Nothing is re-enrolled and no data is lost either way.
./lawlietnever re-issues a certificate that a CA issued. It only re-issues self-signed ones.
6.2 Check the certificate#
On the server#
sudo ./lawliet status # section "TLS certificate": coverage of every address
sudo ./lawliet tls status # state, mode, subject, expiry, agents on plain HTTP
sudo ./lawliet tls # subject and expiry; warns within 30 days; answer N to change nothingWhat you see:
| Line | Meaning |
|---|---|
OK Certificate covers 10.0.0.5, lawliet.corp.example, localhost | Every address of this server is covered |
! certificate does not cover <address>; run: sudo ./lawliet tls repair | Self-signed and an address is missing. See 6.5 |
! certificate does not cover <address>; ask your CA for one that names it, then: sudo ./lawliet tls | CA-issued. See 6.4 |
! Expires within 30 days. | Renew it now |
In the console#
- Open Settings > Platform and find the card Certificate.
- Press Check again to read the certificate again.
- What you see: one of the statuses below, and the details under it.
| Status | Meaning |
|---|---|
| Valid | Covers every address in use, more than 30 days left |
| Expiring soon | Under 30 days left |
| Action needed | Under 7 days, expired, not valid yet, or an address in use is not named |
| No TLS | Plain-HTTP (direct) mode |
| Could not read | The backend could not reach nginx to read it |
The card also shows the subject, issuer (with a self-signed tag), key, validity, the names in the certificate, each address in use marked covered or not covered, the SHA-256 fingerprint with a copy button, and the commands to run on the server. The notification bell announces each change of state once.
6.3 Export the certificate for agent hosts#
Needed only for a self-signed certificate.
sudo ./lawliet tls --export # writes ./lawliet-server.pem
sudo ./lawliet tls --export /tmp/srv.pem # or another pathWhat you see: File, Subject, Expires. The file contains the certificate only, never the key. Copy it to each agent host as /path/to/lawliet-server.pem (Linux, macOS) or C:\path\to\lawliet-server.pem (Windows), or change that path in the commands ./lawliet token prints.
If it fails: No certificate to export. This deployment is not serving TLS. means plain-HTTP mode.
6.4 Install a certificate from your CA#
Use this to renew a CA-issued certificate, or to replace a self-signed one with a CA-issued one.
| Risk | A wrong pair would stop HTTPS. Agents that trust a certificate file (self-signed installs) lose the connection unless they are handed the new certificate first |
| Rollback | Automatic if nginx refuses the new pair. Otherwise run this procedure again with the previous pair (keep a copy of infra/docker/ssl/ before you start) |
Before you start#
- Get from your CA a certificate whose subjectAltName lists every address operators and agents use: the DNS name, and an
IP:entry for each IP address agents connect by. - Have the full chain (server certificate, then intermediates, ideally ending with the root) and the private key, in PEM, on the server.
- Keep a copy:
sudo cp -a infra/docker/ssl /root/ssl-before-$(date +%F).
Procedure#
Run:
sudo ./lawliet tlsAnswer Replace the certificate? (y/N) with
y.Enter Path to the certificate (fullchain PEM): and Path to the private key (PEM):.
It checks that the key belongs to the certificate:
OK Certificate and key match.With no agents enrolled, it installs the pair, tests it with nginx and reloads nginx:
OK Installed,OK nginx reloaded with the new certificate.With agents enrolled, and the chain ending in its root certificate, it first hands that root to the agents (6.5, With agents enrolled) and switches when they hold it. If the chain does not end in its root, or the root is a public CA's, it installs the pair at once and prints the refresh commands for every agent (6.6).
Check:
sudo ./lawliet status, and open the console in a new browser window.
The names and IPv4 addresses the new certificate covers are added to TRUSTED_HOSTS.
Handing a public CA's root to agents#
If the chain ends in a root that is in this server's system trust store (a public CA), it is not handed to agents: they would then trust every certificate that CA issues, for any name. The command says so. Add --allow-public-root only if you accept that:
sudo ./lawliet tls --allow-public-rootWhat you see: the same steps as the procedure above; the root is then handed to the agents with the certificate. The option works the same way with tls repair (6.5).
6.5 Re-issue a self-signed certificate (tls repair)#
Use when status, doctor or the console says the self-signed certificate does not cover an address, has expired, or expires within 30 days.
sudo ./lawliet tls repairIt makes a new self-signed certificate (RSA 2048, 825 days) that keeps every name the old one had and adds every address this server is reached by: the deployment address, the server's own IPv4 addresses, and localhost. When nothing is missing and the certificate is not near expiry it says The certificate already covers ... and changes nothing.
With no agents enrolled#
It re-issues and installs at once. What you see:
OK Installed
OK nginx reloaded with the new certificate
Covers 10.0.0.5, lawliet.corp.example, localhost
Valid until <date>
Previous pair kept as infra/docker/ssl/{fullchain,privkey}.pem.backup.<time>
OK No agent was enrolled, so none needs anything.With agents enrolled#
| Risk | Agents older than 2.1.9.s, and agents offline during the switch, go offline until each host is given the new certificate (6.6). No data is lost and nothing is re-enrolled |
| Rollback | Before the switch: sudo ./lawliet tls cancel. After the switch, the previous pair is kept as infra/docker/ssl/{fullchain,privkey}.pem.backup.<time>. Going back to it is another certificate change: install it with sudo ./lawliet tls (6.4), which hands it to the agents again first |
Run
sudo ./lawliet tls repair. It lists up to 20 enrolled agents and explains what will happen.Answer Re-issue the certificate now? (y/N) with
y(or use--yes).Handing the new certificate to the agents first. The server announces the new certificate in every agent's next heartbeat. Agents 2.1.9.s and later add it to the certificates they trust while the server still serves the old one. Every 10 seconds it prints:
12 of 14 agent(s) hold the new certificate (85%)When every agent that can take it holds it, it switches:
OK Every agent that can be handed it holds it, then Switching to the new certificate, andOK Agents drop the previous certificate once they have checked the server with the new one.If some agents still do not hold it after the wait (10 minutes by default), it lists them with their version and why (
waiting,offline,old), and asks Switch now anyway? Those agents go offline until each is given the new certificate (y/N).- No (the default, and always with
--yes): nothing is switched. The new certificate stays announced and agents keep taking it. Run the same command later to go on waiting. - Yes: it switches; then do 6.6 for the listed hosts.
- No (the default, and always with
Check:
sudo ./lawliet statusand the Environment page: agents should stay online.
Options#
| Option | Meaning |
|---|---|
--yes, -y | Do not ask before re-issuing. Never answers Switch now anyway?: with --yes it waits and stops if not everyone holds it |
--timeout S | Seconds to wait for agents (default 600) |
--ack P | Switch once P percent of eligible agents hold it (default 100) |
--force | Switch at the end of the wait, whoever holds it |
--allow-public-root | See 6.4 |
Examples:
sudo ./lawliet tls repair --yes # wait up to 10 minutes for every agent
sudo ./lawliet tls repair --yes --timeout 1800 # wait 30 minutes
sudo ./lawliet tls repair --yes --ack 90 # switch once 90 % hold it
sudo ./lawliet tls repair --yes --force # switch after the wait, whoever holds itResuming. Running tls repair again while a certificate is still announced resumes with the same certificate: Resuming: the certificate staged by an earlier run is still being handed to the agents.
Agents that trust the system store. Some agents trust their operating system's certificate store rather than a file Lawliet manages. The command lists them; they keep working only if that store trusts the new certificate.
While TLS is off tls repair works the same (443 keeps serving the certificate to agents enrolled over HTTPS) and leaves TLS off.
6.6 Give the new certificate to agents that did not take it#
For agents older than 2.1.9.s, agents that were offline during a switch, and every agent after a switch without hand-over. The command that switched prints these steps with the exact download line for your server.
On the server, export the certificate:
sudo ./lawliet tls --export # writes ./lawliet-server.pemCopy
lawliet-server.pemto each listed host.On each Linux host, run the refresh line printed by the server. Its core is:
sudo bash install.sh --refresh-ca --ca-cert /path/to/lawliet-server.pemOn each Windows host, in PowerShell as Administrator, run the printed line. Its core is:
powershell -NoProfile -ExecutionPolicy Bypass -File .\install.ps1 -RefreshCa -CaCert 'C:\path\to\lawliet-server.pem'
What you see: the installer checks the server with the new certificate first (a wrong file changes nothing), replaces the agent's trust file (keeping the old one as server-ca.pem.previous) and restarts the agent. It reconnects within a minute and keeps its identity.
6.7 Withdraw a staged certificate (tls cancel)#
Use when you started a certificate change with agents enrolled and decided not to finish it.
sudo ./lawliet tls cancelWhat you see: Withdrawn: agents remove the announced certificate at their next heartbeat. nginx was not changed. or No certificate was staged; nothing to withdraw.
It refuses when the staged certificate is already the one nginx serves (the switch has happened): The staged certificate is the one this server already serves, so it cannot be withdrawn; it is recorded as switched.
6.8 Turn encryption off and on#
On an air-gapped site where a self-signed certificate cannot be given to hosts in time, tls off serves the console and API on plain HTTP. It is a deliberate step down: operator passwords, session tokens, the enrolment secret and agent traffic cross the network unencrypted. Every console page says so until it is turned back on.
| TLS on (default) | TLS off | |
|---|---|---|
| Console and API | https://<address> | http://<address> (port 80) |
| Port 80 | Redirects browsers to HTTPS; answers the agent API only for agents enrolled over plain HTTP | The whole console and API |
| Port 443 | Console and API | Still up with the same certificate, so agents enrolled over HTTPS keep working |
| New agent install commands | https:// | http:// with --insecure |
| HSTS | Sent | Not sent |
Turn encryption off#
| Risk | Everything above crosses the network in clear while off |
| Rollback | sudo ./lawliet tls on |
Run:
sudo ./lawliet tls offRead the warning and answer Turn encryption off? (y/N) with
y(or pass--yes).It saves
.env,.lawliet.confand the certificate to.tls-snapshots/tls-switch-<time>/, recreates nginx and the backend, waits for health and checks the result.What you see:
OK Serving as configured, the console URL onhttp://, the HTTPS address kept for agents enrolled over https, and the red warning.
Turn encryption on#
Run:
sudo ./lawliet tls onIt saves the configuration, keeps the certificate (or asks for one if
infra/docker/ssl/is empty), recreates nginx and the backend and verifies HTTPS, HSTS, the redirect, and that plain HTTP refuses callers that are not plain-HTTP agents.If the self-signed certificate misses an address, it runs the
tls repaircheck (6.5).It then asks, each default yes (treat what crossed in clear as captured):
Question What yes does Sign every user out now (all sessions are revoked)? Every session issued so far is refused; everyone signs in again Revoke these API keys? (only if any were used while off) Revokes them. Issue new ones under Settings > API keys Rotate the agent enrolment secret? New install commands ( ./lawliet token). Enrolled agents are unaffected--yesanswers yes to all three.Afterwards: everyone who signed in while encryption was off should change their password, and agents enrolled while off are listed under Environment to be reinstalled over HTTPS.
Agents still on plain HTTP are a critical finding. An agent's token also signs the commands it runs as root, and that token crossed the network in clear. While TLS is on and any agent still uses plain HTTP, the console shows a critical finding and sudo ./lawliet tls status counts them. Reinstall each over HTTPS.
Browsers and HSTS. A host name that received HSTS while TLS was on stays https-only in that browser until its HSTS entry is cleared; 443 keeps answering, so the console still works over https there. Browsers ignore HSTS for IP addresses.
If a switch fails#
The command checks the switched deployment for up to 3 minutes. If it does not behave as it should, it puts the saved configuration back automatically: Reverted: TLS is <before> again, with the configuration saved in <snapshot>. Read sudo ./lawliet logs nginx and sudo ./lawliet logs backend.
6.9 Move a plain-HTTP install to HTTPS#
A deployment installed with option 2 (plain HTTP on 3000 and 8000) is moved behind nginx by the same command:
sudo ./lawliet tls on| Risk | The platform is down for a minute or two while the containers are replaced |
| Rollback | Automatic if the new setup does not come up: Reverted to the direct install (plain HTTP on 3000 and 8000). A database backup is also taken first |
- It explains the move and asks Continue? (Y/n).
- It asks for the certificate (your own or self-signed, as at install).
- It takes a database backup, replaces the containers, and starts nginx with HTTPS on 443. nginx takes over ports 3000 and 8000, so agents installed against
http://<address>:8000keep reporting, and port 3000 sends browsers to HTTPS. - It runs the certificate check and the three questions of 6.8.
- What you see:
OK Serving over HTTPSand the new console URLhttps://<address>.
Then open 443/tcp on the firewall for operators and agents, reinstall the agents over HTTPS one by one (Environment lists them), and close 3000 and 8000 when none is left.
6.10 If it fails#
| Message | Cause and fix |
|---|---|
That certificate and key do not match. nginx would refuse to start. | Give the key that belongs to the certificate |
nginx refused the new certificate (nginx -t failed). The previous certificate and key are restored | The files are not valid PEM, or the chain is broken. Nothing changed; the lines above show nginx's reason |
Could not read the enrolled agents (is the platform running?), so nothing was switched. | Start the platform and run again. --force switches without handing the certificate over (then do 6.6 for every agent) |
The new certificate could not be handed to the agents first then Nothing was switched | The backend could not stage it. Check sudo ./lawliet logs backend, run again, or use --force |
The current and the new certificate cannot be trusted side by side | They have the same name without key identifiers. It switches without hand-over; do 6.6 |
A certificate authority issued it, so Lawliet does not re-issue it | Get a new certificate from your CA (6.4) |
openssl is needed / This openssl ... cannot check which addresses a certificate covers | Install OpenSSL 1.0.2 or later from the OS media |
Could not save the current configuration to .tls-snapshots | The directory must be a real directory owned by you, not a link. Nothing was changed |
| Every agent offline right after a certificate change | They did not take the new certificate. Do 6.6 |
| Agent install fails with curl error 60 | The certificate does not name the address used. 6.5 or 6.4 |

