# Deploying the ATT Healthcheck Server
Single-tenant PHP/SQLite app. Tiny — runs comfortably on the smallest VPS tier.
## Requirements
- **PHP 8.1+ is required.** The code uses typed properties, `str_starts_with`,
`mixed`/`never` return types, named arguments, and constructor property
promotion. PHP 7.x will not even parse the source files. PHP 7.2 has also
been EOL since November 2020 — don't run a public service on it.
Confirm with `php -v`. If the box is on an older default, install a current
PHP alongside it (Ondrej Sury's repo is the standard on Debian/Ubuntu) and
point this vhost at the new FPM socket.
- PHP extensions: `pdo`, `pdo_sqlite`, `json`, `mbstring` (all standard).
- Apache + `mod_rewrite`, or nginx with a `try_files` fallback.
- HTTPS terminating in front of PHP. Plain HTTP is technically accepted, but the plugin client will refuse to talk to non-`https://` endpoints.
## Layout on the server
Anywhere works — every path inside the app is resolved relative to its own files.
Example using `/home/www/healthcheck`:
```
/home/www/healthcheck/
├── public/ ← docroot
│ ├── .htaccess
│ └── index.php
├── src/
├── migrations/
├── data/ ← SQLite file lives here (writable by web user)
├── config.php ← deployment-specific, NOT in git
└── config.php.example
```
> **Perm gotcha for `/home/...` locations:** home directories often default to
> `750` or `700`, which blocks the web user from traversing into the app.
> `sudo chmod 755 /home/www` once is usually enough. `/var/www` doesn't have
> this problem (always world-readable by default).
## First-time install
```sh
# As you, on the server:
sudo mkdir -p /home/www/healthcheck
sudo chown $USER:www-data /home/www/healthcheck
sudo chmod 755 /home/www # see perm gotcha above
# From your dev box:
rsync -avz --exclude='data/' --exclude='config.php' \
~/dev/att-site-healthcheck/server/ \
user@vps:/home/www/healthcheck/
# Back on the server:
cd /home/www/healthcheck
cp config.php.example config.php
php -r 'echo bin2hex(random_bytes(32)), PHP_EOL;' # generate api_key
$EDITOR config.php # paste it in
mkdir -p data
sudo chown -R www-data:www-data data/ config.php
sudo chmod 640 config.php
sudo chmod 770 data/
```
## Apache vhost (example)
```apache
ServerName healthcheck-history.example.com
DocumentRoot /home/www/healthcheck/public
AllowOverride All
Require all granted
# Pass Authorization header through to PHP — Apache strips it by default with FPM.
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/healthcheck-history.example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/healthcheck-history.example.com/privkey.pem
```
## nginx (example)
```nginx
server {
listen 443 ssl http2;
server_name healthcheck-history.example.com;
root /home/www/healthcheck/public;
index index.php;
location / {
try_files $uri /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
# Version-agnostic — Ondrej Sury's packaging installs this symlink
# pointing at whichever php-fpm is the current "alternative". If the
# symlink doesn't exist on your box, hardcode e.g. php8.3-fpm.sock.
# Check with: ls -la /run/php/
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
}
ssl_certificate /etc/letsencrypt/live/healthcheck-history.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/healthcheck-history.example.com/privkey.pem;
}
```
> **Gotcha:** nginx loads *every* file in `sites-enabled/` regardless of
> extension. Don't `cp healthcheck.conf healthcheck.conf.bak` inside that
> directory — the backup becomes a second vhost claiming the same
> `server_name`, and nginx silently ignores one of them ("conflicting server
> name … ignored" in the error log) which manifests as the new config not
> taking effect. Back up *outside* `sites-enabled/`.
## TLS
Use Let's Encrypt + certbot. Example:
```sh
sudo certbot --apache -d healthcheck-history.example.com
# or for nginx:
sudo certbot --nginx -d healthcheck-history.example.com
```
## Configure the plugin to talk to it
In `wp-config.php` on each WordPress install:
```php
define('ATT_HC_API_URL', 'https://healthcheck-history.example.com');
define('ATT_HC_API_KEY', '');
```
## Backups
The SQLite DB is a single file. Snapshot it on a cron:
```cron
# Daily at 03:15, rotate 14 days. SQLite-safe online backup.
15 3 * * * /usr/bin/sqlite3 /home/www/healthcheck/data/att_hc.sqlite ".backup '/var/backups/att-hc/att_hc-$(date +\%F).sqlite'" && find /var/backups/att-hc -name 'att_hc-*.sqlite' -mtime +14 -delete
```
Pull those backups offsite with `rsync` / `rclone` to whatever you already use.
## Updating
Just rsync the source again — migrations run on first request after deploy.
```sh
rsync -avz --delete --exclude='data/' --exclude='config.php' \
~/dev/att-site-healthcheck/server/ \
user@vps:/home/www/healthcheck/
```
## Quick health check
```sh
curl https://healthcheck-history.example.com/
# → {"ok":true,"service":"att-site-healthcheck-server","version":"..."}
```