# ๐Ÿ” 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.