# ๐ Quickstart
This page goes from an installed host to a running server you control: a
configuration on disk, a validated compile, a server you can start, stop, and
watch, and a verification step that proves the file server answered. It assumes
the [installation](/start/install/) is done.
## ๐งพ Before you start
The installer left a service running on port 80, and that service holds the
configuration in `/etc/Pingclair/Pingclairfile`. Stop it while you experiment so
the ports are free:
```bash
sudo pc service stop
```
```bash
mkdir -p ~/demo/public
cd ~/demo
echo '
hello from ~/demo/public
' > public/index.html
```
## 1. โ๏ธ Write a configuration
Create `~/demo/Pingclairfile`:
```caddyfile
{
admin 127.0.0.1:2019
}
http://localhost:8080 {
file_server ./public
}
```
Three things are worth naming. The unnamed block at the top holds global
options, and `admin` is what lets `pingclair start`, `stop`, and `reload` talk to
the running server. The site address carries the scheme, and `http://` is what
forces plaintext; without it Pingclair treats `localhost` as a name and serves
HTTPS from its own certificate authority, which a plain HTTP client sees as an
empty reply ([HTTPS](/start/https/)). The `file_server` root is relative to the
working directory.
## 2. โ
Validate before you run
```bash
pingclair validate
```
```text
โ
Configuration 'Pingclairfile' is valid!
```
`validate` reads `./Pingclairfile` by default and also detects `./Caddyfile`. It
compiles the configuration and applies semantic checks, such as whether
certificate paths exist. Validation is not advisory: a configuration that fails
does not run, and a failing one prints the reason on the last line.
## 3. ๐งญ Read what the configuration becomes
```bash
pingclair adapt --pretty
```
```text
{
"debug": false,
"servers": [
{
"name": "localhost",
"names": [
"localhost"
],
"listen": [
"[::]:8080"
],
```
The compiled JSON is the form the server actually runs. When a directive does
not behave as the documentation says, this is the first place to look. To see
what `pingclair fmt` would change in the file instead:
```bash
pingclair fmt --diff
```
```text
- file_server ./public
+ file_server ./public
```
`fmt` prints the canonical form, which indents with two spaces.
## 4. ๐ Run it
In the foreground, where the log stays attached to your terminal:
```bash
pingclair run Pingclairfile
```
```text
๐ Starting Pingclair with config: Pingclairfile
๐ Starting Pingclair v0.2.0-rc.3
๐ Loaded configuration from: Pingclairfile
๐ง Configured 1 server(s)
๐ Auto HTTPS: enabled
```
Add `--watch` to reload the configuration every time you save it, which is the
development loop:
```bash
pingclair run --watch Pingclairfile
```
```text
โป๏ธ Configuration reloaded successfully
โ
Configuration reloaded completed successfully in 2.478622ms
```
Or run it in the background, where it survives your shell:
```bash
pingclair start -c Pingclairfile
```
```text
โ
Pingclair started in the background (pid 4432)
```
`pingclair start`, `stop`, and `reload` reach the running server through the
Admin API, which is why the configuration above sets `admin`. `pingclair run`
does not need it.
## 5. ๐ Verify
```bash
curl -i http://localhost:8080/
```
```text
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 34
Last-Modified: Tue, 22 Sep 2026 03:26:39 GMT
ETag: "22-6ab1f56f"
Vary: Accept-Encoding
Accept-Ranges: bytes
server: Pingclair
```
`ETag` and `Last-Modified` mean the file server read the file from disk. The
body is `public/index.html`. To stop a background server:
```bash
pingclair stop
```
```text
โ
Pingclair stopped
```
## โก Servers in one command
Three subcommands serve without a configuration file, which is useful for
trying something out or for a throwaway host:
```bash
pingclair file-server --listen :8081 --root ./public
pingclair reverse-proxy --from :8082 --to 127.0.0.1:8081
pingclair respond --listen :8083 -s 200 -b "hello from respond"
```
Each prints its listener on startup:
```text
๐ Starting file server on :8081 serving ./public (browse: false)
๐ Starting reverse proxy: :8082 -> ["127.0.0.1:8081"]
Server address: [::]:8083
```
Every request to `:8082` is proxied to the file server on `:8081`, and `:8083`
answers with the body you passed. `respond` is for development only.
## ๐ Move it into the service
The service runs `/etc/Pingclair/Pingclairfile`, so putting your configuration
there is what makes it survive a reboot:
```bash
sudo cp Pingclairfile /etc/Pingclair/Pingclairfile
sudo pingclair validate /etc/Pingclair/Pingclairfile
sudo pc service reload
curl -i http://localhost/
```
`pc service reload` asks the running server to read the file again, which the
unit does by sending `SIGUSR1`. `pingclair reload` reaches the same code through
the Admin API and also reports what the server thought of the file, which needs
the `admin` option from the global options block; and
`sudo kill -USR1 "$(systemctl show -p MainPID --value pingclair)"` does it with
neither.
Validate first either way, and read the answer afterwards: `systemctl reload`
reports only that the signal was delivered, so the server's verdict โ applied,
or refused with a reason โ is on the unit's status line and in the journal. A
refused reload leaves the previous configuration serving, which is the point of
refusing. [Run it as a service](/start/service/#-what-a-reload-means) is the long
version.
## โ ๏ธ When it does not work
- **`Address already in use`.** The installer's service still holds `:80`, or
another process holds your port. `sudo ss -ltnp | grep :80` names the owner;
`sudo pc service stop` frees the default one.
- **`Empty reply from server` on `http://localhost:8080`.** You are speaking
plaintext to a TLS listener. Add the `http://` scheme to the site address, or
talk to it with `https://` and trust the internal certificate.
- **`Cannot reach admin API at 127.0.0.1:2019`.** The configuration has no
`admin` option, so nothing is listening for `pingclair stop` and
`pingclair reload`. Add it to the global options block, or stop the foreground
process with Ctrl-C.
- **`curl` hangs on a loopback address.** A system proxy is intercepting the
request. Repeat it with `curl --noproxy '*'`.
- **Validation fails with `Unsupported feature`.** The directive is recognized
but not implemented, and the message names the alternative, as in
`encode br`: Brotli is not implemented for proxied responses, so the message
points at `encode zstd gzip`.
## ๐งญ Next steps
- [HTTPS](/start/https/): certificates for a public name, from Let's Encrypt or
the internal authority.
- [Run it as a service](/start/service/): the unit, its reload semantics, and
its logs.
- [Pingclairfile](/reference/pingclairfile/): the language itself, including
matchers, snippets, and imports.