Run it as a service
The installer creates and enables a systemd unit. This page explains its
settings, service commands, and procedures for investigating startup failures
and rejected configuration reloads.
🧾 What the unit does
Section titled “🧾 What the unit does”systemctl cat pingclairThe relevant unit settings are:
[Service]Type=notifyNotifyAccess=mainUser=pingclairGroup=pingclairAmbientCapabilities=CAP_NET_BIND_SERVICECapabilityBoundingSet=CAP_NET_BIND_SERVICEEnvironment="RUST_LOG=info"ExecStart=/usr/local/bin/pingclair run /etc/Pingclair/PingclairfileExecReload=/bin/kill -USR1 $MAINPIDWorkingDirectory=/var/lib/pingclairRestart=on-failureRestartPreventExitStatus=1RestartSec=5sLimitNOFILE=1048576LimitNPROC=512ProtectSystem=fullPrivateTmp=trueNoNewPrivileges=trueThese settings control readiness, permissions, reloads, and restarts:
Type=notifyandNotifyAccess=main: the server notifiessystemdwhen its listeners are bound, sosystemctl startwaits for listener readiness.User=pingclairwithAmbientCapabilities=CAP_NET_BIND_SERVICE: the server runs unprivileged and can still bind ports 80 and 443.- The unit does not set
PINGCLAIR_TLS_STORE. The service account’s home is/var/lib/pingclair, so certificates resolve to/var/lib/pingclair/.local/share/pingclair— the binary’s default, created by the installer and printed bypingclair environ. Setting the variable here would duplicate what the home directory already determines. - The unit does not run
validatethroughExecStartPre.systemdappliesRestartPreventExitStatus=to the main process, not to a pre-command, so a failing pre-command would be retried every five seconds instead of leaving the unit failed. The server compiles the file itself before binding listeners and exits 1 on a refused configuration — the exit code the restart policy prevents. ExecReloadsendsSIGUSR1to reload the configuration.SIGHUPis ignored; a unit that sent it reported success while the old configuration kept serving (issue #66). Becausesystemdcan only observe thatkillexited, the server publishes its reload result on the unit’s status line —Serving (reloaded 1 listener(s) in 323.341µs), orReload rejected: …— visible insystemctl status. The reload section below covers this in detail.Restart=on-failurewithRestartPreventExitStatus=1andRestartSec=5s: exit code 1 means the configuration or the certificate store could not be loaded, so the unit remainsfailedfor investigation without repeated restart attempts. Any other failure is restarted.ProtectSystem=full,PrivateTmp,NoNewPrivileges,LimitNPROC, andLimitNOFILE: these settings restrict filesystem access, privilege escalation, and process resources.
Both installation methods write the same unit. The installer embeds an exact
copy of scripts/pingclair.service, and just repo-lint fails when the two
drift. A fresh curl | bash install and a checkout install produce the same
unit, and systemd-analyze verify reports no warnings for it on either path.
🎛️ Service commands
Section titled “🎛️ Service commands”pc service wraps systemctl for this unit, so the two are interchangeable:
| Task | With pc |
With systemctl |
|---|---|---|
| Start | sudo pc service start |
sudo systemctl start pingclair |
| Stop | sudo pc service stop |
sudo systemctl stop pingclair |
| Reload the configuration | sudo pc service reload |
sudo systemctl reload pingclair |
| Restart, after a listener or a process-wide change | sudo pc service restart |
sudo systemctl restart pingclair |
| State | pc service status |
systemctl status pingclair |
| Follow the log | — | journalctl -u pingclair -f |
pc service status displays the unit state and the server’s readiness status:
● pingclair.service - Pingclair High-Performance Web Server Loaded: loaded (/etc/systemd/system/pingclair.service; enabled; preset: enabled) Active: active (running) since Tue 2026-09-22 05:57:21 UTC; 18s ago Docs: https://pingclair.com/start/service/ Main PID: 27630 (pingclair) Status: "Serving"🔁 What a reload means
Section titled “🔁 What a reload means”An edited /etc/Pingclair/Pingclairfile reaches the running server through one
signal, and two commands send it.
SIGUSR1 is the reload signal and requires no configuration:
sudo kill -USR1 "$(systemctl show -p MainPID --value pingclair)"pc service reload — or sudo systemctl reload pingclair, which is the same
call — sends that signal for you. The unit’s ExecReload is
/bin/kill -USR1 $MAINPID, so this command performs the intended reload. A unit
that sent SIGHUP instead reported success and applied nothing, which is what
issue #66 recorded.
pingclair reload reaches the same code through the Admin API and reports
whether the server accepted the file. This path requires the admin option from
the global options block:
✅ Configuration reloaded successfullyError: ❌ Reload failed (400): HTTP/1.1 400 Bad Requestsystemctl reload can report one thing only: that kill delivered the signal.
The server reads the file afterwards and reports the reload result on the
unit’s status line and in the journal. pc service reload reports signal
delivery and provides commands for checking the reload result:
$ sudo pc service reload✅ Reload signal delivered to pingclair.serviceℹ️ The result lands a moment later: `systemctl status pingclair` or `journalctl -u pingclair -n 20`$ systemctl status pingclair --no-pager | grep Status Status: "Serving (reloaded 1 listener(s) in 323.341µs)"If the server cannot apply the new configuration, the previous configuration
remains active and the status line identifies the rejected change.
Moving the site from :80 to :8080 is the common case, because listener
topology is rebuilt with the sockets at startup:
Status: "Reload rejected: listener topology changed (added: ["[::]:8080"], removed: ["[::]:80"]); restart Pingclair to rebuild H1, H2, H3, and TLS together"A configuration that fails compilation leaves the previous configuration active. Validate the file before reloading:
sudo pingclair validate /etc/Pingclair/PingclairfileChanges to startup-fixed global policies, such as trusted_proxies, are refused
by reload and take effect after a restart. Process-log settings can reload. Use
sudo pc service restart. A configuration that adds or moves a listener is
refused the same way — the status line names the addresses that were added and
removed — because reload applies policy, not a new listening socket.
🛑 What a stop means
Section titled “🛑 What a stop means”systemctl stop sends SIGTERM. The server makes /ready return 503, stops accepting new requests, and lets active requests finish within grace_period (30 seconds by default). Remaining QUIC connections close when the drain ends. A restart has a connection gap between processes; prefer reload for site policy changes.
📜 Logs
Section titled “📜 Logs”The unit sets RUST_LOG=info and sends everything to the journal:
sudo journalctl -u pingclair -fsudo journalctl -u pingclair --since '10 min ago'Startup, reloads, certificate operations, and one access line per request appear there:
INFO pingclair::run: 📄 Loaded configuration from: /etc/Pingclair/PingclairfileINFO pingclair::run: 🔔 Received SIGUSR1, reloading configuration from: /etc/Pingclair/PingclairfileINFO pingclair::run: ✅ Configuration reload completed successfully in 323.341µsINFO pingclair::run: 📊 1 listener(s) updatedINFO pingclair_proxy::server: 📝 Access request_id="65c09fa25d457-6" method="GET" host="localhost" path="/" status=200 bytes=18747 duration_ms=0 remote_ip=::1 user_agent="curl/8.18.0"A reload the server refuses is logged the same way, with the reason and a note that nothing changed:
ERROR pingclair::run: ❌ Configuration reload rejected after 414.491µs: listener topology changed (added: ["[::]:8080"], removed: ["[::]:80"]); restart Pingclair to rebuild H1, H2, H3, and TLS together kind=RestartRequiredERROR pingclair::run: 💡 Previous configuration remains active, unchangedFor a log of its own, with rotation, configure a log sink and write it under
/var/log/pingclair, which the installer creates and gives to the service user.
⚠️ Service startup failures
Section titled “⚠️ Service startup failures”is-activereportsactivatingandNRestartscontinues to increase. The unit was written by an older installer, which had two faults. It carriedRestart=alwayswith noRestartPreventExitStatus, and it ranvalidateas anExecStartPrecommand, whichRestartPreventExitStatusdoes not cover — so a configuration the compiler refuses was retried every five seconds and left the unit repeatedly attempting startup. The installed unit carriesRestart=on-failure+RestartPreventExitStatus=1and no pre-command, and a refused start leavesis-activeatfailedwithNRestartsat zero. On an older install, stop the loop before debugging:sudo systemctl stop pingclair, fix the file, thensudo systemctl reset-failed pingclair.Job for pingclair.service failed because the control process exited with error code. The server refused the configuration before it bound anything, and the compiler’s reason is in the journal, for exampleError: ❌ Configuration Error: Compile error: Unsupported feature: `encode br`: Brotli is not implemented for proxied responses; use `encode zstd gzip`.TLS store /var/lib/pingclair/.local/share/pingclair is not writable: Permission denied. The store belongs to the service account. Checksudo ls -ld /var/lib/pingclair/.local/share/pingclair; it should be owned bypingclair.systemd-analyze verifyreportsMissing '=', ignoring linefor the installed unit. An older installer wrote a unit whose comments had been expanded by the shell — 25 lines of--helpoutput, whichsystemdignores. Reinstalling from the current installer writes the unit verbatim and resolves the invalid unit contents.- The unit is running but external requests fail. Check the provider’s firewall and then the host’s, as on the install page.
🧭 Next steps
Section titled “🧭 Next steps”- Upgrading and removing: files retained during an upgrade or removal.
- HTTPS: certificates, including where the store lives and why
pingclair trustneedsPINGCLAIR_TLS_STORE. log: the access-log sink this page reads from the journal.
