# ๐ 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
```bash
systemctl cat pingclair
```
The relevant unit settings are:
```text
[Service]
Type=notify
NotifyAccess=main
User=pingclair
Group=pingclair
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
Environment="RUST_LOG=info"
ExecStart=/usr/local/bin/pingclair run /etc/Pingclair/Pingclairfile
ExecReload=/bin/kill -USR1 $MAINPID
WorkingDirectory=/var/lib/pingclair
Restart=on-failure
RestartPreventExitStatus=1
RestartSec=5s
LimitNOFILE=1048576
LimitNPROC=512
ProtectSystem=full
PrivateTmp=true
NoNewPrivileges=true
```
These settings control readiness, permissions, reloads, and restarts:
- `Type=notify` and `NotifyAccess=main`: the server notifies `systemd` when its
listeners are bound, so `systemctl start` waits for listener readiness.
- `User=pingclair` with `AmbientCapabilities=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 by `pingclair environ`. Setting the variable here
would duplicate what the home directory already determines.
- The unit does not run `validate` through `ExecStartPre`. `systemd` applies
`RestartPreventExitStatus=` 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.
- `ExecReload` sends `SIGUSR1` to reload the configuration. `SIGHUP` is ignored; a unit that sent it
reported success
while the old configuration kept serving
([issue #66](https://github.com/dorianverlaine/pingclair/issues/66)). Because
`systemd` can only observe that `kill` exited, the server publishes its
reload result on the unit's status line โ `Serving (reloaded 1 listener(s) in
323.341ยตs)`, or `Reload rejected: โฆ` โ visible in `systemctl status`. The
[reload section](#-what-a-reload-means) below covers this in detail.
- `Restart=on-failure` with `RestartPreventExitStatus=1` and `RestartSec=5s`:
exit code 1 means the configuration or the certificate store could not be
loaded, so the unit remains `failed` for investigation without repeated
restart attempts. Any other failure is restarted.
- `ProtectSystem=full`, `PrivateTmp`, `NoNewPrivileges`, `LimitNPROC`, and
`LimitNOFILE`: 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
`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:
```text
โ 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
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:
```bash
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](https://github.com/dorianverlaine/pingclair/issues/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:
```text
โ
Configuration reloaded successfully
```
```text
Error: โ Reload failed (400): HTTP/1.1 400 Bad Request
```
`systemctl 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:
```text
$ 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:
```text
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:
```bash
sudo pingclair validate /etc/Pingclair/Pingclairfile
```
Changes 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
`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
The unit sets `RUST_LOG=info` and sends everything to the journal:
```bash
sudo journalctl -u pingclair -f
sudo journalctl -u pingclair --since '10 min ago'
```
Startup, reloads, certificate operations, and one access line per request appear
there:
```text
INFO pingclair::run: ๐ Loaded configuration from: /etc/Pingclair/Pingclairfile
INFO pingclair::run: ๐ Received SIGUSR1, reloading configuration from: /etc/Pingclair/Pingclairfile
INFO pingclair::run: โ
Configuration reload completed successfully in 323.341ยตs
INFO pingclair::run: ๐ 1 listener(s) updated
INFO 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:
```text
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=RestartRequired
ERROR pingclair::run: ๐ก Previous configuration remains active, unchanged
```
For 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
- **`is-active` reports `activating` and `NRestarts` continues to increase.** The unit
was written by an older installer, which had two faults. It carried
`Restart=always` with no `RestartPreventExitStatus`, and it ran `validate` as
an `ExecStartPre` command, which `RestartPreventExitStatus` does not cover โ
so a configuration the compiler refuses was retried every five seconds and
left the unit repeatedly attempting startup. The
installed unit carries `Restart=on-failure` + `RestartPreventExitStatus=1` and
no pre-command, and a refused start leaves `is-active` at `failed` with
`NRestarts` at zero. On an older install, stop the loop before debugging:
`sudo systemctl stop pingclair`, fix the file, then
`sudo 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 example
``Error: โ 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. Check
`sudo ls -ld /var/lib/pingclair/.local/share/pingclair`; it should be owned by `pingclair`.
- **`systemd-analyze verify` reports `Missing '=', ignoring line` for the
installed unit.** An older installer wrote a unit whose comments had
been expanded by the shell โ 25 lines of `--help` output, which `systemd`
ignores. 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](/start/install/).
## ๐งญ Next steps
- [Upgrading and removing](/start/upgrade/): files retained during an upgrade
or removal.
- [HTTPS](/start/https/): certificates, including where the store lives and why
`pingclair trust` needs `PINGCLAIR_TLS_STORE`.
- [`log`](/reference/directives/#log): the access-log sink this page reads from
the journal.