Self-Hosting
Run your own Krillnotes Relay on shared hosting, a VPS, or Docker.
The Krillnotes Relay is a PHP application built on the Slim 4 framework with SQLite for storage. It’s designed to run on inexpensive shared hosting — you don’t need root access, a VPS, or Docker (though both are supported).
Requirements
- PHP 8.3+
- ext-sodium — for Ed25519/X25519 cryptography (libsodium)
- ext-pdo_sqlite — SQLite database driver
- Composer — PHP dependency manager
- A web server with URL rewriting (Apache with
mod_rewrite, or Nginx)
Most shared hosts running PHP 8.3 already have these extensions enabled.
Quick start
git clone https://github.com/2pisoftware/krillnotes-relay.git
cd krillnotes-relay
composer install --no-dev --optimize-autoloader
php bin/install.php
The install.php script creates the SQLite database, runs all migrations, and
sets up the storage/bundles/ and storage/invites/ directories.
Shared hosting setup
Most shared hosts give you a public directory (often public_html/) and SSH or
FTP access. The key requirement is pointing the web-accessible root at the
Relay’s public/ directory — not the project root.
Directory layout
Upload the project so that only the public/ directory is web-accessible:
your-hosting-root/
├── relay/ ← project root (NOT web-accessible)
│ ├── bin/
│ ├── config/
│ ├── migrations/
│ ├── src/
│ ├── storage/
│ │ ├── database/relay.sqlite
│ │ ├── bundles/
│ │ └── invites/
│ ├── vendor/
│ └── public/ ← point your domain/subdomain here
│ ├── index.php
│ └── .htaccess
If your host lets you set the document root per subdomain, point it at
relay/public/. If not, you can symlink or move the public/ contents into
public_html/ and adjust the path in public/index.php.
Apache
The Relay ships with a .htaccess file in public/ that handles URL
rewriting. Make sure your host has mod_rewrite enabled and AllowOverride All
set for the directory.
Nginx
server {
listen 443 ssl;
server_name relay.yourdomain.com;
root /path/to/relay/public;
index index.php;
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\. {
deny all;
}
}
File permissions
chmod 750 storage/database storage/bundles storage/invites
The web server process needs read/write access to storage/. The project root,
src/, and config/ should not be directly web-accessible.
Configuration
All settings are in config/settings.php. The defaults are sensible for most
deployments, but you can tune:
| Setting | Default | Description |
|---|---|---|
limits.max_bundle_size_bytes | 10 MB | Maximum size of a single sync bundle |
limits.max_storage_per_account_bytes | 100 MB | Total storage per account |
limits.bundle_retention_days | 30 | Days before bundles are auto-deleted |
limits.account_deletion_grace_days | 90 | Grace period before permanent account deletion |
auth.session_lifetime_seconds | 30 days | How long a session token stays valid |
auth.challenge_lifetime_seconds | 5 min | PoP challenge expiry |
Admin access
The Relay includes a web-based admin panel at /admin and a CLI dashboard.
Setting up admin credentials
php bin/admin-password.php
This prompts for a password and outputs an Argon2id hash. Set it as an environment variable:
export ADMIN_PASSWORD_HASH='$argon2id$v=19$m=65536,t=4,p=1$...'
On shared hosting, you can set environment variables in .htaccess:
SetEnv ADMIN_PASSWORD_HASH "$argon2id$v=19$m=65536,t=4,p=1$..."
SetEnv ADMIN_USERNAME "admin"
Or in the hosting control panel’s environment variable settings.
Admin CLI
If you have SSH access:
php bin/admin.php # Full dashboard
php bin/admin.php accounts # List accounts
php bin/admin.php health # DB stats, storage, settings
php bin/admin.php help # All commands
Cleanup cron job
Expired bundles, sessions, invites, and flagged accounts need periodic cleanup. Add an hourly cron job:
0 * * * * /usr/bin/php /path/to/relay/bin/cleanup.php >> /var/log/relay-cleanup.log 2>&1
On shared hosting, use the hosting control panel’s cron scheduler. The command is just:
php /path/to/relay/bin/cleanup.php
HTTPS
The Relay should always run over HTTPS in production. Session cookies are
marked Secure on HTTPS connections, and the security headers middleware
enforces HSTS.
On shared hosting, most providers offer free Let’s Encrypt certificates through the control panel. On a VPS, use Certbot or your web server’s TLS configuration.
Docker
For VPS or local development, a Docker setup is included:
docker compose up --build
This starts the Relay at localhost:8080 with default admin credentials from
.env.docker.
Connecting Krillnotes to your relay
Once your relay is running, configure Krillnotes to use it in
Settings → Sync → Relay URL. Enter the URL of your relay instance
(e.g. https://relay.yourdomain.com).
Automated deployment
The repository includes a GitHub Actions workflow (.github/workflows/deploy-ftp-dev.yml)
that runs composer install --no-dev in CI and FTP-deploys changed files on
push to main. It never overwrites storage/database/ or storage/bundles/,
so live data is safe on every deploy.
Required GitHub secrets:
| Secret | Example |
|---|---|
FTP_SERVER | ftp.yourhost.com |
FTP_USERNAME | ftpuser |
FTP_PASSWORD | password |
FTP_SERVER_DIR | /relay/ |
After the first deploy, SSH in and run php bin/install.php to initialise the
database.
Security notes
- The Relay never decrypts bundle content — all encryption is end-to-end
- Transport metadata (workspace IDs, device keys, timing, bundle sizes) is visible to the relay operator, similar to what an email server sees
- Proof-of-Possession prevents key spoofing during device registration
- Rate limiting protects auth endpoints (10 attempts per 15 min, 5 for login)
- Bundle polling is rate-limited to 1 request per 60 seconds per account
- Self-hosting eliminates third-party metadata exposure entirely