pingd docs

Configuration

pingd reads its configuration from environment variables. There is no config file. Defaults are tuned for local development; production deployments need to set at least an admin password, a data directory, and (for browser pushes) a VAPID config.

Core

The repository also includes .env.example with the same variables in copyable form.

VariableDefaultPurpose
ADMIN_USERNAMEadminAdmin username, seeded on first run
ADMIN_PASSWORDchangemeAdmin password, seeded on first run
PINGD_DATA_DIRdataDirectory for SQLite database and CLI config
PINGD_ALLOW_REGISTRATIONfalseIf true, exposes POST /auth/register
PINGD_GUEST_ENABLEDtrueIf false, disables POST /auth/guest
PINGD_RESERVED_TOPIC_NAMESunsetComma-separated topic names that users cannot create; case-insensitive
PINGD_DEFAULT_PUBLIC_READfalseDefault publicRead for newly created topics when omitted
PINGD_DEFAULT_PUBLIC_PUBLISHfalseDefault publicPublish for newly created topics when omitted
PINGD_DEFAULT_SHARE_TOKEN_TTLunsetDefault share-token expiry when expiresAt is omitted; accepts 30d, 12h, 5m, 60s, or seconds
PINGD_DEFAULT_PERMISSION_TTLunsetDefault permission expiry when expiresAt is omitted; same duration format
PINGD_MAX_TOPICS_PER_USERunsetMaximum topics per non-admin user; unset means unlimited
PINGD_MAX_SHARE_TOKENS_PER_TOPICunsetMaximum active share tokens per topic; unset means unlimited
PINGD_LOG_FORMATtexttext or json
LOG_LEVELenvironmentVapor log level (trace, debug, info, ...)
Always override ADMIN_PASSWORD on first boot. It is only used to seed the very first user; once the database exists, changing it has no effect. Rotate the admin password through the dashboard or API.

HTTP

pingd listens on port 7685 by default. The port is fixed at startup; change the host port mapping (-p 8080:7685) if you want to expose it differently.

VariableDefaultPurpose
PORT7685HTTP listen port inside the container/process
PINGD_CORS_ORIGIN** or comma-separated list of allowed origins
PINGD_RATE_LIMIT_COUNT30API requests/minute per IP
PINGD_PUBLISH_RATE_LIMIT_PER_USER_PER_MINunsetPublish requests/minute per authenticated user; unset means unlimited
PINGD_ANON_PUBLISH_RATE_LIMIT_PER_IP_PER_MINunsetPublish requests/minute per anonymous client IP; unset means unlimited
PINGD_WEBHOOK_RATE_LIMIT_PER_TOKEN120Webhook receives/minute per token
PINGD_WEBHOOK_RATE_LIMIT_PER_IP300Webhook receives/minute per IP

Allowed CORS request headers: Authorization, Content-Type, Origin, X-Requested-With, X-Topic-Token, and X-Push-Token.

APNS (iOS push)

APNS can run in either mode:

The simplest setup is to point at a relay you trust:

PINGD_APNS_MODE=relay
PINGD_APNS_RELAY_BASE_URL=https://pingd.dev
PINGD_APNS_RELAY_TOKEN=<bearer token>

Leave PINGD_APNS_MODE unset for the mock provider, which logs pushes but sends nothing. Full walkthrough in APNS.

VariableModePurpose
PINGD_APNS_MODEbothdirect, relay, or unset for mock
PINGD_APNS_KEY_PATHdirectPath to Apple .p8 key file
PINGD_APNS_KEY_IDdirectApple key ID
PINGD_APNS_TEAM_IDdirectApple team ID
PINGD_APNS_BUNDLE_IDdirectApp bundle identifier
PINGD_APNS_ENVdirectdevelopment or production (default)
PINGD_APNS_RELAY_BASE_URLrelayBase URL of the upstream pingd in direct mode
PINGD_APNS_RELAY_TOKENrelayBearer token for the relay server

Web Push (browsers)

VariablePurpose
PINGD_WEBPUSH_VAPID_CONFIGJSON with contactInformation, primaryKey, expirationDuration, validityDuration

Generate the value once per deployment with the bundled keygen:

docker compose exec pingd ./pingd-webpush-keygen --email admin@example.com
swift run pingd-webpush-keygen --email admin@example.com

When this variable is unset, GET /webpush/vapid-key returns 404 and Web Push deliveries are treated as no-op deliveries. Full setup in Web Push.

Logging

Request logs include request ID, method, path, status, and duration. Audit logs cover security-sensitive events: login, guest creation, user changes, topic changes, device registration, subscriptions, permissions, token changes, and webhook activity.

Use JSON logs in production:

PINGD_LOG_FORMAT=json
LOG_LEVEL=info

Reverse proxy setup

pingd serves plain HTTP and does not terminate TLS. In production, run it behind a reverse proxy that handles TLS and forwards requests to pingd. The proxy must send a clean client IP in X-Real-IP or X-Forwarded-For so pingd's rate limiter sees the real client instead of the proxy.

Below are minimal working configs for Caddy and Nginx. Adapt hostnames, ports, and TLS paths to your deployment.

Caddy

pingd.example.com {
    reverse_proxy localhost:7685 {
        header_up X-Real-IP {remote_host}

        flush_interval -1
        transport http {
            read_timeout 1h
        }
    }
}

Nginx

upstream pingd {
    server 127.0.0.1:7685;
    keepalive 32;
}

server {
    listen 443 ssl http2;
    server_name pingd.example.com;

    ssl_certificate     /etc/letsencrypt/live/pingd.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/pingd.example.com/privkey.pem;

    location / {
        proxy_pass http://pingd;
        proxy_http_version 1.1;

        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host  $host;
        proxy_set_header Connection        "";

        # SSE: keep the stream open and unbuffered
        proxy_buffering    off;
        proxy_cache        off;
        proxy_read_timeout 1h;
        proxy_send_timeout 1h;
    }
}

server {
    listen 80;
    server_name pingd.example.com;
    return 301 https://$host$request_uri;
}

What pingd needs from the proxy

Rate limiting

Rate limits are configured with the variables above. The general API limit is per client IP. Publish and webhook routes have separate limits so they can be tuned without changing the rest of the API.