Security AWS

Onetime Secret Self-Hosted Secret Sharing on AWS User Guide

| Product: Onetime Secret

Overview

Onetime Secret is an open source service for sharing sensitive information safely. A sender pastes a secret - a password, an API key, a private note - and receives a unique one-time link. The recipient opens that link exactly once to reveal the secret, after which it is permanently destroyed and the link returns not-found. Secrets can carry an optional passphrase and an expiry, so nothing lingers in inboxes or chat history. This cloudimg image runs the Onetime Secret application under Puma bound to the loopback address 127.0.0.1:3000, backed by Redis bound to 127.0.0.1:6379 with a password, and fronts both with an nginx reverse proxy that terminates TLS on port 443. Port 80 redirects to HTTPS and serves an unauthenticated /healthz endpoint for load balancers. A unique application secret, Redis password and administrator (colonel) account are generated on the first boot of every instance, so no shared or default credential ships in the image. The Redis secret store lives on a dedicated data volume mounted at /var/lib/redis. Backed by 24/7 cloudimg support.

What is included:

  • Onetime Secret 0.26.1, built from source and managed by systemd, serving a full web UI over HTTPS
  • nginx on port 443 terminating TLS with a self-signed certificate regenerated per instance; port 80 redirects to HTTPS
  • The application bound to the loopback address 127.0.0.1:3000 and Redis bound to 127.0.0.1:6379, neither exposed to the network
  • Redis protected with a unique password so the secret store is never reachable anonymously
  • A unique application secret, Redis password and administrator account generated on first boot and recorded in a root-only file
  • Open account self-registration disabled, with no reachable default administrator credential
  • A dedicated data volume at /var/lib/redis holding the Redis secret store
  • redis-server.service, onetimesecret-web.service and nginx.service as systemd units, enabled and active
  • An unauthenticated /healthz endpoint for load-balancer health checks
  • 24/7 cloudimg support

Prerequisites

An AWS account, an EC2 key pair in the target region, and a VPC with a public subnet. m5.large (2 vCPU / 8 GiB RAM) is the recommended instance type. Security group inbound: allow 22/tcp from your management network for SSH, and 443/tcp (and optionally 80/tcp, which only redirects to HTTPS) for the web UI.

Step 1 - Launch the AMI

Subscribe to the listing in AWS Marketplace, choose Continue to Launch, and launch through the EC2 console or the AWS CLI. Select the m5.large instance type, your key pair, and a security group that opens ports 22 and 443.

aws ec2 run-instances \
  --image-id <ami-id> \
  --instance-type m5.large \
  --key-name <your-key-pair> \
  --security-group-ids <security-group-id> \
  --subnet-id <subnet-id> \
  --associate-public-ip-address

The application, Redis and nginx start automatically. On the very first boot a one-shot service generates the per-instance secrets, resolves the instance public address, regenerates the TLS certificate and proves the one-time round-trip before the service is ready - allow a minute or two after the instance reaches the running state.

Step 2 - Connect to your instance

SSH in as the default login user for your OS variant.

OS variant SSH login user
Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> <login-user>@<public-ip>

Step 3 - Confirm the services are running

All three services should report active.

systemctl is-active redis-server onetimesecret-web nginx

Expected output:

active
active
active

Step 4 - Confirm the loopback bindings

The application and Redis must listen only on the loopback interface - nothing on a public address. nginx is the sole network-facing surface.

sudo ss -tlnp | grep -E ':3000|:6379'

Expected - both bound to 127.0.0.1 only:

LISTEN 0 1024 127.0.0.1:3000 0.0.0.0:*
LISTEN 0 511  127.0.0.1:6379 0.0.0.0:*

Confirm Redis rejects unauthenticated access (it requires the per-instance password):

sudo redis-cli ping

Expected:

NOAUTH Authentication required.

Step 5 - Verify the TLS endpoints

Port 80 redirects to HTTPS; the web UI and the health endpoint answer over TLS. The certificate is self-signed per instance, so -k is used here to skip verification from the command line.

curl -s -o /dev/null -w 'http :80 -> %{http_code} %{redirect_url}\n' http://127.0.0.1/
curl -sk -o /dev/null -w 'healthz -> %{http_code}\n' https://127.0.0.1/healthz
curl -sk -o /dev/null -w 'web UI -> %{http_code}\n' https://127.0.0.1/

Expected:

http :80 -> 301 https://127.0.0.1/
healthz -> 200
web UI -> 200

Step 6 - Read your per-instance details

The service URL, the Redis password and the optional administrator (colonel) account are written to a root-only file on first boot.

sudo cat /root/onetimesecret-info.txt

It contains these keys (values are unique to your instance):

ONETIMESECRET_URL=https://<public-ip>/
COLONEL_EMAIL=...
COLONEL_PASSWORD=...
COLONEL_API_TOKEN=...
REDIS_PASSWORD=...

Step 7 - Prove the one-time round-trip from the command line

The image ships a self-test that creates a secret, reveals it once (HTTP 200), then proves a second reveal is refused (HTTP 404 - the secret has self-destructed), and checks the administrator API credential round-trip.

sudo /opt/ots/onetimesecret-selftest.sh

Expected (keys will differ):

round-trip OK: created=... firstReveal=200 secondReveal=404 (one-time proven); colonel API auth=200 wrongToken=401

Step 8 - Share a secret in the browser

Open https://<public-ip>/ in your browser. Because the certificate is self-signed per instance, accept the browser warning (or copy /etc/nginx/tls/onetimesecret.crt from the instance and trust it). Paste a secret, optionally set a passphrase and an expiry, and select Create Link.

Create a self-destructing secret

The service returns a single-use link along with a timeline of when the secret was created and when it expires. Share this link separately from any other details.

One-time link created

Step 9 - Reveal once, then watch it self-destruct

When the recipient opens the link and confirms, the secret is revealed exactly once.

Reveal the secret once

Open the same link a second time and the secret is gone - it has been permanently destroyed.

The secret has self-destructed

Step 10 - Administrator sign-in (optional)

Onetime Secret can be used entirely anonymously - creating and revealing secrets needs no account. The optional administrator (colonel) account signs in at https://<public-ip>/signin with the COLONEL_EMAIL and COLONEL_PASSWORD from the info file, for the admin dashboard and the API. Open account self-registration is disabled on this image.

Administrator sign-in

Step 11 - Confirm the dedicated data volume

The Redis secret store lives on its own EBS volume, separate from the OS disk and independently resizable.

df -h /var/lib/redis | tail -1

Expected - a device distinct from the root filesystem mounted at /var/lib/redis:

/dev/nvme1n1  20G  ...  /var/lib/redis

Maintenance

  • Services: systemctl status redis-server onetimesecret-web nginx and journalctl -u onetimesecret-web for application logs.
  • TLS: the per-instance certificate is at /etc/nginx/tls/onetimesecret.crt. To use your own domain and certificate, terminate TLS at an Application Load Balancer or replace the certificate and key and reload nginx.
  • Backups: snapshot the /var/lib/redis data volume with EBS snapshots or AWS Backup.
  • Version: the baked release is recorded at /var/lib/onetimesecret/VERSION.

Support

24/7 technical support is available from cloudimg by email and chat at support@cloudimg.co.uk - deployment, terminating TLS with your own domain and certificate, configuring expiry and passphrase policy, Redis backup planning, and integrating the Onetime Secret API into your workflows.