> ## Documentation Index
> Fetch the complete documentation index at: https://veriqa.app/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Install as an OS service

> Install the Veriqa auth server from an archive as a systemd unit or a Windows service — no Docker, no .NET on the machine.

This is the same standalone auth server as the [self-hosted quickstart](/docs/quickstart/self-hosted),
delivered without Docker: an archive with a **self-contained** executable plus an installer that
registers it as a systemd unit or a Windows service. The target machine needs no .NET runtime.

What this page covers is the **installation** — where the files live, how the service is registered,
how it is updated and removed. What to put into the configuration (stores, certificates, clients,
channels) does not change between delivery modes and stays in the
[self-hosted quickstart](/docs/quickstart/self-hosted).

<Note>
  **The installer leaves you with a running service, not with a production one.** It writes a
  start-up configuration — an in-memory store and two self-signed token certificates — so that the
  service answers `/health/live` straight away instead of refusing to start. Everything that makes it
  a production instance is [step 6](#6-move-to-production).
</Note>

## 1. Download the archive and check it

A release carries three archives and one checksum file:

| File | What it is |
| - | - |
| `veriqa-authserver-<version>-linux-x64.tar.gz` | Linux, x86-64 |
| `veriqa-authserver-<version>-linux-arm64.tar.gz` | Linux, ARM64 |
| `veriqa-authserver-<version>-win-x64.zip` | Windows, x86-64 |
| `veriqa-authserver-<version>-SHA256SUMS.txt` | SHA-256 sums of the three archives |

They are release assets, and the base of their address is stable — `permalink/latest` always
resolves to the newest release:

```
<repository>/-/releases/permalink/latest/downloads/veriqa-authserver-<version>-linux-x64.tar.gz
```

The file name carries the version, so the address as a whole is **not** a permanent link: once the
next release is out, a path with the previous version is no longer among the assets
`permalink/latest` resolves to, and the download answers 404. Take the repository address and the
current version from [veriqa.app/source](https://veriqa.app/source) each time instead of keeping a
ready-made URL:

```bash theme={null}
VERIQA_VERSION=0.6.0    # version of the release you are installing
VERIQA_DOWNLOADS=…      # …/-/releases/permalink/latest/downloads, from veriqa.app/source

curl -fLO "$VERIQA_DOWNLOADS/veriqa-authserver-$VERIQA_VERSION-linux-x64.tar.gz"
curl -fLO "$VERIQA_DOWNLOADS/veriqa-authserver-$VERIQA_VERSION-SHA256SUMS.txt"

sha256sum --ignore-missing -c "veriqa-authserver-$VERIQA_VERSION-SHA256SUMS.txt"
```

`--ignore-missing` is what makes this work with a single archive downloaded: the sums file lists all
three, and without the flag `sha256sum` fails on the two you did not take.

On Windows:

```powershell theme={null}
$version = "0.6.0"
$expected = (Select-String -Path ".\veriqa-authserver-$version-SHA256SUMS.txt" -Pattern "win-x64\.zip").Line.Split(" ")[0]
(Get-FileHash ".\veriqa-authserver-$version-win-x64.zip" -Algorithm SHA256).Hash -eq $expected
```

`True` — the archive is intact.

## 2. Install on Linux

The archive unpacks into a single `veriqa-authserver/` directory and carries its own installer,
`install.sh`. Run it as root **from that directory**:

```bash theme={null}
tar -xzf "veriqa-authserver-$VERIQA_VERSION-linux-x64.tar.gz"
cd veriqa-authserver
sudo ./install.sh
```

The installer creates the system user `veriqa` (no login shell, no home directory created), copies
the program, creates the configuration and data directories, generates the token certificates,
writes the start-up configuration, installs the unit file and enables the service. It does **not**
start it: the start-up configuration is meant to be read before the first start, not after it.

| Option | Default | What it sets |
| - | - | - |
| `--install-directory <path>` | `/opt/veriqa` | Program directory |
| `--config-directory <path>` | `/etc/veriqa` | Configuration directory |
| `--urls <urls>` | `http://127.0.0.1:8080` | Addresses the service listens on |
| `--service-name <name>` | `veriqa` | Name of the systemd unit |

<Note>
  **The program directory belongs to this installation alone.** Both installers write into it
  without clearing it out first — a file of the archive overwrites its counterpart, anything else
  stays where it is — and removal is the mirror image: `--uninstall` / `-Uninstall` takes away the
  files the installer copied and nothing else, and keeps the directory itself if something else is
  still in there. Point `--install-directory` / `-InstallDirectory` at a directory of its own and the
  installation comes off in one step.
</Note>

The default `--urls` is **loopback only** — the instance is reachable from the same machine and from
a reverse proxy on it, and from nowhere else.

<Warning>
  **TLS is needed for the API itself, not only to open the instance up.** Over plain HTTP the token
  endpoint answers `400 invalid_request` — "This server only accepts HTTPS requests"
  ([ID2083](https://documentation.openiddict.com/errors/ID2083)) — so a backend calling the
  confirmation API gets no token, even from the same machine. Do [step 7](#7-https) before the first
  call: put a reverse proxy in front, or give Kestrel a certificate.
</Warning>

Start the service when you have read what the installer printed:

```bash theme={null}
sudo systemctl start veriqa
systemctl status veriqa
```

<Warning>
  The installer refuses to run over a service that is already registered under the same name
  (`use --uninstall first`) rather than recreate it silently. Updating an existing installation is
  [Updating](#updating).
</Warning>

## 3. Install on Windows

Unpack the `.zip` and run `install.ps1` from the unpacked directory in an **elevated** PowerShell
(Windows PowerShell 5.1 and PowerShell 7 both work):

```powershell theme={null}
Expand-Archive ".\veriqa-authserver-$version-win-x64.zip" -DestinationPath .
cd .\veriqa-authserver
.\install.ps1
```

| Parameter | Default | What it sets |
| - | - | - |
| `-InstallDirectory <path>` | `%ProgramFiles%\Veriqa` | Program directory |
| `-Urls <urls>` | `http://127.0.0.1:8080` | Addresses the service listens on |
| `-ServiceName <name>` | `Veriqa` | Name of the Windows service |

The service is registered with automatic start under the virtual account
`NT SERVICE\<ServiceName>` — no account and no password of your own. The configuration directory
is `%ProgramData%\Veriqa`; its access list is rebuilt without inheritance, so the `Users` group gets
no access to the secrets kept there. As on Linux, the service is registered but left stopped:

```powershell theme={null}
Start-Service Veriqa
Get-Service Veriqa
```

## 4. What the first start gives you

The installation is deliberately complete enough to start and no more:

* the OpenIddict store is `InMemory` — registered clients, issued tokens and the journal are **lost
  on every restart**;
* the token certificates are self-signed, RSA-2048, valid for two years, and were generated on this
  machine;
* no channel and no OIDC client is configured, so a real sign-in cannot complete yet.

Both health endpoints answer 200 in this state:

```bash theme={null}
curl -f http://127.0.0.1:8080/health/live
curl -f http://127.0.0.1:8080/health/ready
```

`/health/ready` is 200 because the start-up configuration declares no external dependency to check —
not because the instance is ready for production.

## 5. Where the files are

The configuration, the data-protection keys and the token certificates live **outside** the program
directory, so that replacing the program never touches them.

| What | Linux | Windows |
| - | - | - |
| Program | `/opt/veriqa` | `%ProgramFiles%\Veriqa` |
| Configuration | `/etc/veriqa/appsettings.Production.json` | `%ProgramData%\Veriqa\appsettings.Production.json` |
| Sample production configuration | `/opt/veriqa/appsettings.Production.sample.json` | `%ProgramFiles%\Veriqa\appsettings.Production.sample.json` |
| Data-protection keys | `/var/lib/veriqa/keys` | `%ProgramData%\Veriqa\keys` |
| Token certificates | `/var/lib/veriqa/certs` | `%ProgramData%\Veriqa\certs` |
| Service definition | `/etc/systemd/system/veriqa.service` | Registry key of the `Veriqa` service |

On Linux `/etc/veriqa` is `root:veriqa` `0750` and `/var/lib/veriqa` is `0700` owned by `veriqa`:
they hold secrets, and only the service account reads them.

**Why the keys directory is not optional.** Data-protection keys protect the sign-in cookies and
every value the host encrypts. With no keys directory and no Redis the host keeps them in memory, and
every restart invalidates what the previous process issued — users are thrown out of sign-ins that
were in flight. The installer therefore sets `Veriqa:DataProtection:KeysDirectory` in the environment
of the service itself, and the directory survives an update and an ordinary uninstall.

The service is registered with four environment variables — the environment is where they belong,
because they say where the configuration is read from:

| Variable | Linux | Windows |
| - | - | - |
| `ASPNETCORE_ENVIRONMENT` | `Production` | `Production` |
| `ASPNETCORE_URLS` | value of `--urls` | value of `-Urls` |
| `VERIQA_CONFIG_DIRECTORY` | `/etc/veriqa` | `%ProgramData%\Veriqa` |
| `Veriqa__DataProtection__KeysDirectory` | `/var/lib/veriqa/keys` | `%ProgramData%\Veriqa\keys` |

### The configuration directory

`VERIQA_CONFIG_DIRECTORY` names a directory, not a file. From it the host reads `appsettings.json`
and `appsettings.Production.json`, both optional, and layers them **over** the files shipped with the
program. Precedence, weakest to strongest:

1. `appsettings.json` of the program directory;
2. `appsettings.Production.json` of the program directory;
3. `appsettings.json` of the configuration directory;
4. `appsettings.Production.json` of the configuration directory;
5. environment variables (`Veriqa__OpenIddict__…`);
6. command-line arguments.

That is why the one line the installer writes — `Provider` = `InMemory` — wins over the
`Provider` = `PostgreSQL` that ships in the program directory, and why anything you pass through the
environment still wins over both.

If the variable names a directory that does not exist, the host refuses to start and says so:
`VERIQA_CONFIG_DIRECTORY points to '/etc/veriqa', which does not exist.` A directory that exists but
holds no files is not an error.

<Note>
  The self-hosted quickstart keeps the integrator's own settings in `config/veriqa.json`, resolved
  against the content root. For a service the content root is the **program** directory, which an
  update replaces — so in this delivery mode put your settings into the configuration directory
  instead.
</Note>

## 6. Move to production

The installation ships a template next to the program:
`appsettings.Production.sample.json`. It carries the same keys as the self-hosted quickstart, with
`REPLACE_ME` placeholders for the values. Use it as a **reference** and edit the configuration file
the installer wrote, rather than overwriting that file with it: the template carries no paths to the
certificates generated on this machine, and every `REPLACE_ME` in it is a value you still have to
supply.

```bash theme={null}
sudo cp /etc/veriqa/appsettings.Production.json /root/appsettings.Production.json.bak
sudo nano /etc/veriqa/appsettings.Production.json   # reference: /opt/veriqa/appsettings.Production.sample.json
sudo systemctl restart veriqa
```

Keep that copy **outside** `/etc/veriqa`: from the edit below on it holds secrets — a bot token,
connection strings, certificate passwords — and [full removal](#removing) neither takes it away on
Linux nor spares it on Windows, where `%ProgramData%\Veriqa` goes whole.

What to write there is documented once, for both delivery modes:

* **stores** — a relational provider and its connection string, and the audit sink:
  [self-hosted, step 2](/docs/quickstart/self-hosted#2-configure-it);
* **token certificates** — real ones instead of the self-signed pair the installer generated:
  [self-hosted, step 3](/docs/quickstart/self-hosted#3-provide-the-token-certificates);
* **OIDC clients** — [self-hosted, step 4](/docs/quickstart/self-hosted#4-declare-your-client);
* **channels** — [self-hosted, step 7](/docs/quickstart/self-hosted#7-enable-channels), and
  [Channel setup](/docs/guides/channels) end to end.

<Warning>
  Replacing the token certificates invalidates every token signed with the old pair, and switching
  the store from `InMemory` to a database starts from an empty one: the clients registered in memory
  have to be declared in the configuration. Do both before the instance carries real sign-ins.
</Warning>

### SQL Server on Windows

The Windows archive does not include `Microsoft.Data.SqlClient.SNI.dll`, the native network library
the SQL Server driver uses on Windows: it is Microsoft's, under the Microsoft Software License Terms,
and Veriqa does not redistribute it. PostgreSQL needs nothing, and neither does Linux. With
`SqlServer` selected for any store and the library missing, the service refuses to start, and the
message names the `Microsoft.Data.SqlClient` version and links its nuget.org page.

Take the library from the NuGet package `Microsoft.Data.SqlClient.SNI.runtime`, in the version listed
under **Dependencies** on that page, and put it next to the program. Downloading it, you accept
Microsoft's terms for it:

```powershell theme={null}
$sniVersion = "…"   # from the Dependencies of Microsoft.Data.SqlClient on nuget.org
$package = "microsoft.data.sqlclient.sni.runtime"
Invoke-WebRequest "https://api.nuget.org/v3-flatcontainer/$package/$sniVersion/$package.$sniVersion.nupkg" -OutFile "$env:TEMP\sni.zip"
Expand-Archive "$env:TEMP\sni.zip" -DestinationPath "$env:TEMP\sni" -Force
Copy-Item "$env:TEMP\sni\runtimes\win-x64\native\Microsoft.Data.SqlClient.SNI.dll" "$env:ProgramFiles\Veriqa\"
Restart-Service Veriqa
```

The installer removes only the files it put in the program directory, so the library stays there
through an [update](#updating); after one, check that the version the new release's driver lists is
still the one you copied. On [full removal](#removing) delete it by hand.

## 7. HTTPS

The host speaks HTTP on the addresses of `--urls` / `-Urls`, and OpenIddict serves `/connect/token`
and the rest of the protocol **only over `https`** — so this step is not optional, whether or not the
instance is reachable from outside. There are two ways to put TLS in front of it.

**(a) A reverse proxy on the same machine.** Leave `--urls` at `http://127.0.0.1:8080`, terminate TLS
in NGINX / Caddy / IIS and forward to loopback. For OpenIddict to build `https` URLs the proxy must
forward `X-Forwarded-Proto` and `X-Forwarded-For`, and the proxy must be trusted — the same
`ForwardedHeaders` section as in
[self-hosted, step 6](/docs/quickstart/self-hosted#6-put-it-behind-a-reverse-proxy), in the configuration
file of the installation:

```json appsettings.Production.json theme={null}
{
  "ForwardedHeaders": {
    "KnownProxies": [ "127.0.0.1" ]
  }
}
```

Only loopback is trusted by default, so a proxy on the same machine is covered even without this
section — writing it out keeps the trust boundary visible in the configuration. A proxy on **another**
host is not covered: put its address here instead, or its network in `KnownIPNetworks`. The proxy is
also where per-IP rate limiting belongs — see
[Production hardening](/docs/guides/hardening#rate-limiting-and-anti-abuse).

**(b) A certificate in Kestrel**, with no proxy at all. This is plain ASP.NET Core configuration in
the same file ([Kestrel endpoints](https://learn.microsoft.com/aspnet/core/fundamentals/servers/kestrel/endpoints)):

```json appsettings.Production.json theme={null}
{
  "Kestrel": {
    "Endpoints": {
      "Https": {
        "Url": "https://0.0.0.0:8443",
        "Certificate": {
          "Path": "/var/lib/veriqa/certs/server.pfx",
          "Password": "…"
        }
      }
    }
  }
}
```

The `Kestrel:Endpoints` section **replaces** the addresses of `ASPNETCORE_URLS` rather than adding to
them, so no reinstall is needed — but the HTTP endpoint the installer set through `--urls` is gone
with it. Declare an `Http` endpoint alongside if you still want one (for local health probes, for
instance). Keep the server certificate readable by the service account only, next to the token
certificates.

## 8. Verify

```bash theme={null}
curl -f http://127.0.0.1:8080/health/live     # the process is up
curl -f http://127.0.0.1:8080/health/ready    # configured dependencies answer
curl https://auth.your-domain.com/.well-known/openid-configuration
```

Discovery must come back with `https` URLs; if it comes back with `http`, the proxy is not trusted —
[step 7](#7-https). The full checklist of a deployed instance, including the first sign-in, is
[self-hosted, step 8](/docs/quickstart/self-hosted#8-verify).

On Linux the service log is in the journal:

```bash theme={null}
journalctl -u veriqa -n 200 --no-pager
```

On Windows a failed start is recorded by the Service Control Manager in Event Viewer → Windows Logs.
To see the message of the host itself, run the executable in a console **from the program
directory**, with the same environment:

```powershell theme={null}
$env:ASPNETCORE_ENVIRONMENT = "Production"
$env:VERIQA_CONFIG_DIRECTORY = "$env:ProgramData\Veriqa"
# Started by hand, the content root is the working directory — `appsettings*.json` and `wwwroot`
# are read from there, so step into the program directory instead of running it by full path.
Set-Location "$env:ProgramFiles\Veriqa"
.\Veriqa.Core.AuthServer.Host.exe
```

## Updating

An update replaces the **program** and keeps the configuration, the data-protection keys and the
token certificates: they live outside the program directory on purpose.

```bash theme={null}
sudo ./install.sh --uninstall          # from the unpacked archive of the installed version
tar -xzf "veriqa-authserver-$VERIQA_VERSION-linux-x64.tar.gz"   # the new version
cd veriqa-authserver && sudo ./install.sh
sudo systemctl start veriqa
```

On Windows the same two steps: `.\install.ps1 -Uninstall` from the unpacked archive of the installed
version, then `.\install.ps1` from the new archive, then `Start-Service Veriqa`.

The second run does not overwrite an existing `appsettings.Production.json` and does not regenerate
existing certificates — it reports that it kept them. If you installed with non-default
`--service-name`, `--install-directory` or `--config-directory`, pass the same values to both runs.

`--urls` / `-Urls` has to be repeated on the installing run as well, and for a different reason: it
is not kept anywhere to be read back. The installer writes the unit file — on Windows, the
environment of the service — anew every time, so an install without it puts the service back on the
default `http://127.0.0.1:8080` and the instance stops answering anything but loopback.

## Removing

**Ordinary removal** takes away the service and the program and **keeps** the configuration, the
data-protection keys and the certificates, so that reinstalling over them keeps current sign-ins and
secrets:

```bash theme={null}
sudo ./install.sh --uninstall
```

```powershell theme={null}
.\install.ps1 -Uninstall
```

Run it **from the unpacked archive of the installed version**: the installer reads that archive to
know which files under the program directory are its own, and takes away those. The program
directory goes with them once it is empty; if anything else is left in it, the directory stays, and
the installer says so instead of clearing it out. Without the archive next to it the installer
removes the service and warns that the program files could not be told apart — it does not guess.

**Full removal** also deletes the configuration the installer wrote, the data directory and (on
Linux) the `veriqa` user:

```bash theme={null}
sudo ./install.sh --uninstall --remove-data
```

```powershell theme={null}
.\install.ps1 -Uninstall -RemoveData
```

<Warning>
  Full removal makes **every issued cookie invalid** — the data-protection keys are gone with the
  directory — and the tokens signed with the removed certificates stop being accepted. Copy
  `/etc/veriqa/appsettings.Production.json` (or `%ProgramData%\Veriqa\appsettings.Production.json`)
  somewhere safe first if this installation is meant to come back. **Somewhere safe means outside
  those directories**: a copy left next to the configuration is not a backup — on Windows it goes
  with `%ProgramData%\Veriqa`, and on Linux it stays behind, secrets and all, because the directory
  survives and the installer does not name what is left in it.
</Warning>

On Linux `/var/lib/veriqa` goes whole, while `/etc/veriqa` loses the `appsettings.Production.json`
the installer wrote and is removed only when nothing else is left in it. On Windows all three live in
`%ProgramData%\Veriqa`, which goes whole.

`--remove-data` / `-RemoveData` without the uninstall switch is refused, and nothing is changed.
Removing twice is safe: with no service left, the installer says so and takes away what is left of
the installation.

## Common failures

| Symptom | Cause | What to do |
| - | - | - |
| A backend gets `400 invalid_request`, "This server only accepts HTTPS requests", from `/connect/token` | The instance is called over plain HTTP: OpenIddict serves the protocol over `https` only | Put TLS in front of it, or make the proxy trusted so that `X-Forwarded-Proto` is read — [step 7](#7-https) |
| Windows: the service does not start, the log says `Microsoft.Data.SqlClient.SNI.dll` is not found | `SqlServer` is selected for a store, and the archive does not ship Microsoft's native library for it | Put the library next to the program — [SQL Server on Windows](#sql-server-on-windows) |
| After a restart the registered clients and the issued tokens are gone | The start-up configuration is still in place: `Veriqa:OpenIddict:Database:Provider` = `InMemory` is the volatile store | Move to a relational provider — [step 6](#6-move-to-production) and [self-hosted, step 2](/docs/quickstart/self-hosted#2-configure-it) |
| The service is active, but every restart throws users out of their sign-ins | The host keeps the data-protection keys in memory: `Veriqa:DataProtection:KeysDirectory` is not in effect (the environment of the service was edited, or the host runs outside the service manager) | Check the four variables of [step 5](#5-where-the-files-are); `systemctl show veriqa -p Environment` on Linux |
| The sign-in page shows untranslated texts | The content root is not the program directory. Under a service manager it always is; started by hand from another directory it is the working directory, and `wwwroot/locales` is read relative to the content root | Start the service through `systemctl` / `Start-Service`, or `cd` into the program directory first |
| Discovery returns `http` URLs, `/connect/authorize` answers `invalid_request` | The proxy is not trusted, so `X-Forwarded-Proto` is ignored | Add the proxy to `ForwardedHeaders:KnownProxies` — [step 7](#7-https) |
| After an update the instance answers on loopback only, though the service is active | The installing run was made without `--urls` / `-Urls`: the unit file (on Windows, the environment of the service) was written anew with the default `http://127.0.0.1:8080` | Reinstall with the address in use: `./install.sh --uninstall`, then `./install.sh --urls <urls>` from the archive — [Updating](#updating) |
| `Access denied` on the keys directory | The directory was created by someone other than the service account — by hand as root, or under a different `--service-name` / `-ServiceName`, whose virtual account is a different one on Windows | Linux: `chown -R veriqa:veriqa /var/lib/veriqa`. Windows: `.\install.ps1 -Uninstall -ServiceName <name>`, then `.\install.ps1 -ServiceName <name>` from the archive — both runs need the name the service was installed under (and the same `-InstallDirectory`, if the installation used one), otherwise they act on the default `Veriqa` service and `%ProgramFiles%\Veriqa` instead. The reinstall rebuilds the access lists and keeps the configuration, the keys and the certificates (over a registered service the installer refuses to run) |

Failures that stop the host **at startup** — a missing certificate, an incomplete client, a channel
without a token, an unknown provider value — are the same in both delivery modes and are listed in
[self-hosted, step 9](/docs/quickstart/self-hosted#9-common-first-run-failures). On Linux the message is in
`journalctl -u veriqa`.

## Building the archives yourself

For the release archives the repository carries a pair of scripts that behave identically —
`build/publish-host.sh` and `build/publish-host.ps1`:

```bash theme={null}
./build/publish-host.sh                      # all three RIDs, Release
./build/publish-host.sh --rid linux-x64      # one of them
```

They publish the host self-contained and single-file for `linux-x64`, `linux-arm64` and `win-x64`,
pack each result together with the installer files of its OS, and leave the archives and
`veriqa-authserver-<version>-SHA256SUMS.txt` in `artifacts/host/`. The version comes from
`build/Versions.props`. What comes out is what this page installs — the same layout, the same
`appsettings.Production.sample.json`, and no `appsettings.Development.json`.

## Next steps

<CardGroup cols={2}>
  <Card title="Self-hosted quickstart" icon="server" href="/docs/quickstart/self-hosted">
    Stores, certificates, clients and channels — the configuration itself.
  </Card>

  <Card title="Channel setup" icon="comments" href="/docs/guides/channels">
    Telegram, WhatsApp, other channels and Email — end to end.
  </Card>

  <Card title="Configuration reference" icon="list" href="/docs/reference/configuration">
    Every section, key and default in one place.
  </Card>

  <Card title="Production hardening" icon="shield-check" href="/docs/guides/hardening">
    What to check before shipping.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.