obscura-server 0.4.2

A server for relaying secure messages using the Signal Protocol
Documentation

Obscura Server

Obscura Server is a minimalist, secure relay server for the Signal Protocol. It facilitates end-to-end encrypted asynchronous messaging while knowing nothing about the content of the messages.

Features

  • Zero-Knowledge Architecture: The server stores opaque encrypted blobs. It cannot read message content.
  • Signal Protocol Support: Uses PreKeys (Identity, Signed, One-Time) to enable X3DH key exchanges.
  • Asynchronous Delivery: Stores encrypted messages until the recipient comes online to fetch them.
  • Encrypted Attachments: Supports secure file uploads via S3-compatible storage with automated expiry.
  • Strict Privacy Limits: Automatic garbage collection of old messages and global inbox limits to prevent metadata buildup.
  • OpenTelemetry Native: Built-in support for Traces, Metrics, and Structured Logging via OTLP.
  • Container Native: Built with Docker in mind for easy deployment.
  • Configurable: Fully configurable via command-line flags or environment variables.

Configuration

Obscura Server can be configured using either command-line options or by setting corresponding environment variables. Command-line options take precedence over environment variables.

General Configuration

Option Description Environment Variable Default Required
--ttl-days Global days before messages/attachments are auto-deleted OBSCURA_TTL_DAYS 30 No

Database Pooling Configuration

Option Description Environment Variable Default
--db-url PostgreSQL connection string OBSCURA_DATABASE_URL postgres://user:password@localhost/signal_server
--db-max-connections Max connections in the pool OBSCURA_DATABASE_MAX_CONNECTIONS 20
--db-min-connections Min connections to keep idle OBSCURA_DATABASE_MIN_CONNECTIONS 5
--db-acquire-timeout-secs Seconds to wait for a connection OBSCURA_DATABASE_ACQUIRE_TIMEOUT_SECS 3
--db-idle-timeout-secs Seconds before an idle connection closes OBSCURA_DATABASE_IDLE_TIMEOUT_SECS 600
--db-max-lifetime-secs Seconds before a connection is recycled OBSCURA_DATABASE_MAX_LIFETIME_SECS 1800

Server Configuration

Option Description Environment Variable Default
--server-host Interface to bind the server to OBSCURA_SERVER_HOST 0.0.0.0
--server-port Port to bind the server to OBSCURA_SERVER_PORT 3000
--mgmt-port Management port for health/metrics OBSCURA_SERVER_MGMT_PORT 9090
--shutdown-timeout-secs Timeout for background tasks during shutdown OBSCURA_SERVER_SHUTDOWN_TIMEOUT_SECS 5
--trusted-proxies CIDR ranges of trusted proxies OBSCURA_SERVER_TRUSTED_PROXIES 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.1/32

Auth Configuration

Option Description Environment Variable Default
--jwt-secret Secret key for signing JWTs OBSCURA_AUTH_JWT_SECRET change_me_in_production
--access-token-ttl-secs Access token time-to-live in seconds OBSCURA_AUTH_TOKEN_TTL_SECS 900
--refresh-token-ttl-days Refresh token time-to-live in days OBSCURA_AUTH_REFRESH_TOKEN_TTL_DAYS 30

Rate Limiting

Option Description Environment Variable Default
--rate-limit-per-second API requests allowed per second OBSCURA_RATE_LIMIT_PER_SECOND 10
--rate-limit-burst Max API burst allowance per IP OBSCURA_RATE_LIMIT_BURST 20
--auth-rate-limit-per-second Auth requests allowed per second OBSCURA_RATE_LIMIT_AUTH_PER_SECOND 1
--auth-rate-limit-burst Auth API burst allowance per IP OBSCURA_RATE_LIMIT_AUTH_BURST 3

Health Configuration

Option Description Environment Variable Default
--db-timeout-ms Timeout for DB health check OBSCURA_HEALTH_DB_TIMEOUT_MS 2000
--storage-timeout-ms Timeout for storage health check OBSCURA_HEALTH_STORAGE_TIMEOUT_MS 2000
--pubsub-timeout-ms Timeout for PubSub health check OBSCURA_HEALTH_PUBSUB_TIMEOUT_MS 2000

Messaging Configuration

Option Description Environment Variable Default
--max-inbox-size Max pending messages per user OBSCURA_MESSAGING_INBOX_MAX_SIZE 1000
--messaging-cleanup-interval-secs How often to run message cleanup OBSCURA_MESSAGING_CLEANUP_INTERVAL_SECS 300
--batch-limit Max messages sent per DB fetch loop OBSCURA_MESSAGING_BATCH_LIMIT 50
--pre-key-refill-threshold Threshold to trigger client refill notification OBSCURA_PRE_KEY_REFILL_THRESHOLD 20
--max-pre-keys Max One-Time PreKeys allowed per user OBSCURA_PRE_KEYS_MAX 100

Notification Configuration

Option Description Environment Variable Default
--gc-interval-secs How often to run notification cleanup OBSCURA_NOTIFICATIONS_GC_INTERVAL_SECS 60
--global-channel-capacity Capacity of global dispatcher channel OBSCURA_NOTIFICATIONS_GLOBAL_CHANNEL_CAPACITY 1024
--user-channel-capacity Capacity of per-user notification channel OBSCURA_NOTIFICATIONS_USER_CHANNEL_CAPACITY 64
--push-delay-secs Grace period before sending a fallback push OBSCURA_NOTIFICATIONS_PUSH_DELAY_SECS 2
--worker-interval-secs Frequency of push queue polling OBSCURA_NOTIFICATIONS_WORKER_INTERVAL_SECS 1
--worker-concurrency Max concurrent push delivery tasks (also Redis poll limit) OBSCURA_NOTIFICATIONS_WORKER_CONCURRENCY 100
--push-queue-key Redis key for the push notification queue OBSCURA_NOTIFICATIONS_PUSH_QUEUE_KEY jobs:push_notifications
--channel-prefix Redis PubSub prefix for user pokes OBSCURA_NOTIFICATIONS_CHANNEL_PREFIX user:
--visibility-timeout-secs How long a push job is leased by a worker OBSCURA_NOTIFICATIONS_VISIBILITY_TIMEOUT_SECS 30
--janitor-interval-secs Frequency of invalid token cleanup flushes OBSCURA_NOTIFICATIONS_JANITOR_INTERVAL_SECS 5
--janitor-batch-size Max invalid tokens deleted per batch OBSCURA_NOTIFICATIONS_JANITOR_BATCH_SIZE 50
--janitor-channel-capacity Capacity of the janitor's token buffer OBSCURA_NOTIFICATIONS_JANITOR_CHANNEL_CAPACITY 256

PubSub Configuration (Distributed Notifications)

Option Description Environment Variable Default
--pubsub-url PubSub connection URL OBSCURA_PUBSUB_URL redis://localhost:6379
--pubsub-min-backoff-secs Min reconnection backoff OBSCURA_PUBSUB_MIN_BACKOFF_SECS 1
--pubsub-max-backoff-secs Max reconnection backoff OBSCURA_PUBSUB_MAX_BACKOFF_SECS 30

WebSocket Configuration

Option Description Environment Variable Default
--ws-outbound-buffer-size WS outbound channel capacity OBSCURA_WS_OUTBOUND_BUFFER_SIZE 32
--ws-ack-buffer-size WS ACK channel capacity OBSCURA_WS_ACK_BUFFER_SIZE 1000
--ws-ack-batch-size WS ACK DB batch size OBSCURA_WS_ACK_BATCH_SIZE 100
--ws-ack-flush-interval-ms WS ACK DB flush interval OBSCURA_WS_ACK_FLUSH_INTERVAL_MS 500

Storage Configuration (Attachments)

Option Description Environment Variable Default
--storage-bucket Storage Bucket Name OBSCURA_STORAGE_BUCKET obscura-attachments
--storage-region Storage Region OBSCURA_STORAGE_REGION us-east-1
--storage-endpoint Custom Storage Endpoint (e.g., for MinIO) OBSCURA_STORAGE_ENDPOINT -
--storage-access-key Storage Access Key ID OBSCURA_STORAGE_ACCESS_KEY -
--storage-secret-key Storage Secret Access Key OBSCURA_STORAGE_SECRET_KEY -
--storage-force-path-style Force Path Style (Required for MinIO) OBSCURA_STORAGE_FORCE_PATH_STYLE false
--storage-max-size-bytes Max attachment size in bytes OBSCURA_STORAGE_MAX_SIZE_BYTES 52428800
--storage-cleanup-interval-secs How often to run attachment cleanup OBSCURA_STORAGE_CLEANUP_INTERVAL_SECS 3600
--storage-cleanup-batch-size Max attachments to delete per batch OBSCURA_STORAGE_CLEANUP_BATCH_SIZE 100

Telemetry Configuration

Option Description Environment Variable Default
--otlp-endpoint OTLP endpoint for traces/metrics OBSCURA_TELEMETRY_OTLP_ENDPOINT -
--log-format Log format (text or json) OBSCURA_TELEMETRY_LOG_FORMAT text
--trace-sampling-ratio Trace sampling ratio (0.0 to 1.0) OBSCURA_TELEMETRY_TRACE_SAMPLING_RATIO 1.0
--metrics-export-interval-secs How often to export metrics OBSCURA_TELEMETRY_METRICS_EXPORT_INTERVAL_SECS 60
--export-timeout-secs OTLP export timeout OBSCURA_TELEMETRY_EXPORT_TIMEOUT_SECS 10

Example

# Using Flags
./obscura-server \
  --database-url postgres://user:pass@localhost/db \
  --jwt-secret my_secret \
  --storage-bucket my-attachments \
  --server-port 8080

# Using Environment Variables
export OBSCURA_DATABASE_URL=postgres://user:pass@localhost/db
export OBSCURA_AUTH_JWT_SECRET=my_secret
export OBSCURA_STORAGE_BUCKET=my-attachments
./obscura-server

Docker

A Dockerfile is included for easy deployment.

Build and Run

  1. Build the image:

    docker build -t obscura-server .
    
  2. Run with Docker:

    docker build -t obscura-server .
    docker run -d \
      -p 3000:3000 \
      -p 9090:9090 \
      -e OBSCURA_DATABASE_URL="postgres://user:pass@host.docker.internal:5432/obscura" \
      -e OBSCURA_PUBSUB_URL="redis://host.docker.internal:6379" \
      -e OBSCURA_AUTH_JWT_SECRET="your_secret_key" \
      -e OBSCURA_STORAGE_BUCKET="obscura-attachments" \
      obscura-server
    

Docker Compose

A docker-compose.yml is provided for a complete local stack (Postgres + Valkey + MinIO + Server):

docker compose up -d

Development

Prerequisites

  • Rust 1.83+
  • PostgreSQL 16+
  • Valkey 8+ (or Redis)
  • protoc (Protocol Buffers compiler)

Running Locally

  1. Start Postgres, MinIO, and Valkey:

    docker compose up -d db minio valkey
    
  2. Run the server:

    export OBSCURA_DATABASE_URL=postgres://user:password@localhost/signal_server
    export OBSCURA_PUBSUB_URL=redis://localhost:6379
    export OBSCURA_AUTH_JWT_SECRET=test
    export OBSCURA_STORAGE_BUCKET=test
    cargo run
    

    Migrations are applied automatically on startup.

Testing

cargo test

Releasing

Releases are managed via GitHub Actions.

  1. Go to the Actions tab in the GitHub repository.
  2. Select the Bump Version & Tag workflow on the left.
  3. Click Run workflow.
  4. Select the Bump Type from the dropdown (patch, minor, or major).
  5. Click Run workflow.

The system will automatically:

  1. Bump the version in Cargo.toml based on your selection.
  2. Commit and Tag the release.
  3. Trigger the Publish Release workflow to build and publish artifacts to Crates.io and GHCR.

API Documentation

  • REST API: Defined in openapi.yaml.
  • WebSocket: Available at /v1/gateway. Expects Protobuf messages defined in proto/obscura/v1/obscura.proto.

License

Copyright (c) 2026 Nolan Cooper

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.