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:

SettingDefaultDescription
limits.max_bundle_size_bytes10 MBMaximum size of a single sync bundle
limits.max_storage_per_account_bytes100 MBTotal storage per account
limits.bundle_retention_days30Days before bundles are auto-deleted
limits.account_deletion_grace_days90Grace period before permanent account deletion
auth.session_lifetime_seconds30 daysHow long a session token stays valid
auth.challenge_lifetime_seconds5 minPoP 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:

SecretExample
FTP_SERVERftp.yourhost.com
FTP_USERNAMEftpuser
FTP_PASSWORDpassword
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