Onetime Secret 0.26.19
Overview
Onetime Secret is an open-source service for sharing a secret through a link that works only once. Paste a password, an API key or a private note, optionally protect it with a passphrase, and send the link. The recipient opens it once and the secret is destroyed. A second visit finds nothing, and an unread secret is deleted when its lifetime runs out.
This image is a hardened, ready-to-use Onetime Secret 0.26.19 server on Ubuntu 24.04 for Amazon EC2. It is one version of the SecureAnyCloud identity, secrets and PKI listing on AWS Marketplace. You pick it as the software version when you subscribe and launch.
What you get:
- nginx in front, Onetime Secret behind it. nginx is the only public listener (ports 80 and 443). Onetime Secret runs under Puma on
127.0.0.1:3000, the loopback address only, as its own unprivileged user, and cannot modify its own code. - HTTPS from the first boot with a certificate generated on your instance (an end-entity certificate, not a certificate authority). Port 80 redirects to HTTPS. The instance's name is set from its own EC2 public DNS name at first boot. You can change it to your own domain.
- Secrets encrypted with a per-instance key. Onetime Secret encrypts every secret before it is stored, with a key derived from an application secret that first boot generates on your instance. Neither the store's append-only file nor its snapshot holds a secret in plain text.
- Per-instance secrets, never in a process environment. The application secret, the session secret and the store password are generated on each instance's first boot. They are kept only in
/etc/onetime/config.yaml(readable by the service, not by nginx or Valkey). They are never on a command line, in a systemd unit or in a process environment. - A per-instance administrator, no default accounts. First boot creates the colonel (Onetime Secret's administrator) with a random password that Onetime Secret itself generates. There are no demo or default accounts, and self-signup is off. Anonymous one-time secret sharing works without an account, and only the accounts you create can sign in.
- Valkey on localhost. Secrets and accounts are stored by Valkey, the Redis-compatible store from Ubuntu 24.04, on
127.0.0.1and::1only, with a per-instance password (Valkey keeps only its SHA-256). Its one account may not run administrative commands. Persistence is on, so accounts and unread secrets survive a reboot. - Real client addresses, fixed host name. nginx passes the real TCP peer address to Onetime Secret and never a client's
Host,X-Forwarded-HostorForwardedheader. Links and e-mails therefore always use the configured name, and rate limits cannot be dodged with a forged header. Requests for any other host name are dropped. - Abuse limits. Anyone who can reach the address can create secrets, because that is how Onetime Secret works without accounts. nginx limits each client address to 30 secret creations, reveals and sign-ins per minute, and 6 sign-in attempts per minute. Onetime Secret adds its own per-address limits. A page on another website cannot create, reveal or burn secrets here.
- No outgoing mail by default. No mail server credentials are baked in. Mail is off until you configure it (Step 9).
- Locked down. TLS 1.2 and 1.3 only. Onetime Secret, nginx, Valkey, the command-line tool and the first-boot service are blocked from the instance metadata service, and the AMI requires IMDSv2.
- Built from source, verified, and fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on. See Step 13 for exactly how it is built.
- On the image: Onetime Secret 0.26.19 (a SecureAnyCloud build of the upstream v0.26.19 tag in /opt/onetimesecret/app, with Ruby 3.4.10 in /opt/onetimesecret/ruby), Valkey 7.2 and nginx from Ubuntu 24.04, cloud-init and the AWS Systems Manager agent.
Default user: ubuntu | Architecture: x86_64 | Recommended instance type: m5.large
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet.
- A security group that allows TCP 22 (SSH) from your own networks only. Allow TCP 443 (the web app) and TCP 80 (redirects to HTTPS) only from the networks whose people should use this service: your office, your VPN, or your VPC CIDR.
- Anyone who can reach port 443 can create secrets, so do not open it to
0.0.0.0/0unless you want a public service. Do not open 3000 or 6379, because Onetime Secret and Valkey listen on the loopback address only. - For production, a DNS name you control (for example
secrets.example.com) and, ideally, a certificate from a certificate authority. Until then, the instance uses its EC2 public DNS name with its own self-signed certificate.
Launch the instance
In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick Onetime Secret 0.26.19 on Ubuntu 24.04 as the software version, and pick your Region. Then choose Launch through EC2.
Choose m5.large, your key pair, and a security group as described in the prerequisites.
Or launch with the AWS CLI. Replace the values in angle brackets:
aws ec2 run-instances --image-id <ami-id-from-the-listing> --instance-type m5.large \
--key-name <your-key-pair> --security-group-ids <your-security-group> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=onetimesecret}]'First boot takes about half a minute after the instance is running.
To have the instance use your own name from the start, put the name in /etc/sac/onetimesecret-domain with user data (for example a cloud-init write_files entry). Otherwise, first boot uses the instance's public DNS name, or its public IP address if it has no DNS name.
Connect to your instance
Operating system SSH user
Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>Check the services
Confirm that the four services are active:
systemctl is-active onetimesecret-firstboot valkey-server onetimesecret nginxactive active active activeOnly SSH, HTTP and HTTPS are public. Onetime Secret (Puma) and Valkey listen on the loopback address only:
sudo ss -tln | grep -E ':(22|80|443|3000|6379) 'LISTEN 0 1024 127.0.0.1:3000 0.0.0.0:* LISTEN 0 511 127.0.0.1:6379 0.0.0.0:* LISTEN 0 511 0.0.0.0:443 0.0.0.0:* LISTEN 0 4096 0.0.0.0:22 0.0.0.0:* LISTEN 0 511 0.0.0.0:80 0.0.0.0:* LISTEN 0 511 [::]:443 [::]:* LISTEN 0 4096 [::]:22 [::]:* LISTEN 0 511 [::]:80 [::]:* LISTEN 0 511 [::1]:6379 [::]:*Read the address and the administrator password
Print the credentials file:
sudo cat /root/onetimesecret-credentials.txtThe file (root only, mode 0600) holds this instance's address, the SHA-256 fingerprint of its certificate and the colonel (administrator) sign-in:
# Onetime Secret 0.26.19 - written on first boot by onetimesecret-firstboot.service. Unique to this instance. ONETIMESECRET_URL=https://ec2-52-55-145-0.compute-1.amazonaws.com/ ONETIMESECRET_TLS_SHA256=DC:64:7E:6B:...:(32 bytes in hex pairs) COLONEL_EMAIL=colonel@onetimesecret.invalid COLONEL_PASSWORD=<20 random characters, unique to this instance>
# Anyone who can reach ONETIMESECRET_URL can create one-time secrets (no account needed, by design). Restrict the
# security group's 80/443 rules to the networks that should use it (your office, VPN or VPC CIDR). # The colonel is the administrator: sign in at https://ec2-52-55-145-0.compute-1.amazonaws.com/signin and open https://ec2-52-55-145-0.compute-1.amazonaws.com/colonel . # Self-signup is OFF (site.authentication.signup in /etc/onetime/config.yaml); outgoing mail is OFF until you set it. # The certificate is self-signed and unique to this instance: compare its SHA-256 fingerprint (above) with the one # your browser shows, or install a CA-issued certificate. Do not add it to a trust store. # This instance's app secret, session secret and store password are in /etc/onetime/config.yaml (0640 root:ots).
# store password check:
sudo onetimesecret-valkey-password check new one:
sudo onetimesecret-valkey-password rotate
# Your own name (point DNS at this instance first):
sudo onetimesecret-set-domain secrets.example.com
# User guide: https://secureanycloud.com/stacks/onetime-secret-0-26-19/The certificate is self-signed and unique to your instance, so your browser warns about it. Open the certificate details in the browser. Check that its SHA-256 fingerprint matches ONETIMESECRET_TLS_SHA256, and only then continue.
Do not add the certificate to your operating system's trust store. For production, replace it with a certificate from a certificate authority (Step 10).
The certificate is an end-entity certificate for this instance's names only. Verify it with:
openssl x509 -in /etc/ssl/onetimesecret/onetimesecret.crt -noout -subject -ext basicConstraints,keyUsage,extendedKeyUsage,subjectAltNamesubject=CN = ec2-52-55-145-0.compute-1.amazonaws.com, O = sac X509v3 Subject Alternative Name: DNS:ec2-52-55-145-0.compute-1.amazonaws.com, IP Address:52.55.145.0, IP Address:172.31.93.253 X509v3 Basic Constraints: critical CA:FALSE X509v3 Key Usage: critical Digital Signature, Key Encipherment X509v3 Extended Key Usage: TLS Web Server AuthenticationShare a secret
Browse to the ONETIMESECRET_URL address. Paste the secret, optionally add a passphrase, and choose how long the link may live.
Choose Create Link. The page shows the one-time link. Send it to the recipient over any channel, and send the passphrase over a different one.
The recipient opens the link, enters the passphrase and reveals the secret.
The secret is now gone. Opening the same link again finds nothing.
Use the API
Onetime Secret has a JSON API that you can call from scripts. Run the script below on the instance itself, with the instance's own certificate as the trust anchor. It creates a secret protected by a passphrase, tries a wrong passphrase, reveals the secret, and tries again:
U=$(sudo sed -n 's/^ONETIMESECRET_URL=//p' /root/onetimesecret-credentials.txt) C=(-s --cacert /etc/ssl/onetimesecret/onetimesecret.crt)
KEY=$(curl "${C[@]}" --data 'secret=api-test-value&passphrase=correct-horse&ttl=3600' "${U}api/v1/share" \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["secret_key"])')
echo "created a secret with a key of ${#KEY} characters" curl "${C[@]}" -o /dev/null -w 'wrong passphrase: %{http_code}\n' --data 'passphrase=wrong' "${U}api/v1/secret/${KEY}" curl "${C[@]}" -w '\nfirst read: %{http_code}\n' --data 'passphrase=correct-horse' "${U}api/v1/secret/${KEY}" | sed -E 's/"secret_key":"[^"]*"/"secret_key":"..."/' curl "${C[@]}" -o /dev/null -w 'second read: %{http_code}\n' --data 'passphrase=correct-horse' "${U}api/v1/secret/${KEY}"created a secret with a key of 62 characters wrong passphrase: 404 {"value":"api-test-value","secret_key":"...","share_domain":"https://ec2-52-55-145-0.compute-1.amazonaws.com"} first read: 200 second read: 404The shortest lifetime Onetime Secret accepts is 60 seconds (ttl, in seconds). A secret nobody reads is deleted when its lifetime runs out.
A request that a page on another website makes (its Origin is not this instance) is refused:
U=$(sudo sed -n 's/^ONETIMESECRET_URL=//p' /root/onetimesecret-credentials.txt)
curl -s --cacert /etc/ssl/onetimesecret/onetimesecret.crt -o /dev/null -w '%{http_code}\n' -H 'Origin: https://evil.example' --data 'secret=x&ttl=3600' "${U}api/v1/share"403Sign in as the colonel (administrator)
Browse to https://<your-instance-name>/signin and sign in with COLONEL_EMAIL and COLONEL_PASSWORD from the credentials file. Then open /colonel for the administration console (users, secrets statistics, system settings).
Only an account with the colonel role can open the console. Anonymous visitors and ordinary accounts are refused.
Change the colonel's password from Account in the web app. The colonel e-mail address is not a real mailbox (colonel@onetimesecret.invalid). To use your own address, create a new colonel and keep or remove the first one (Step 8). Destructive colonel actions ask for the password again (Onetime Secret's step-up window).
Accounts and sign-up
Accounts are optional. Anyone who can reach the address can share secrets without one. An account keeps a list of the secrets you sent.
Self-signup is off, so an administrator creates accounts with Onetime Secret's own command-line tool. The tool prints a generated password once. Never type a password on the command line:
sudo onetimesecret-ots customers create alice@example.comUse --role colonel to create another administrator. sudo onetimesecret-ots customers list shows the accounts, and sudo onetimesecret-ots --help lists every command. The tool runs as the ots service user with metadata access blocked.
To let people create their own accounts, configure outgoing mail first (Step 9), because new accounts must confirm their e-mail address. Then set signup: true under site.authentication in /etc/onetime/config.yaml and restart:
sudo grep -n -A5 '^ authentication:' /etc/onetime/config.yaml14: authentication: 15- enabled: true 16-
# Self-signup is OFF: only accounts an administrator creates can sign in. To open sign-up set signup: true 17-
# (accounts must verify their e-mail address, so configure outgoing mail below first). 18- signup: false 19- signin: truesudo systemctl restart onetimesecretOutgoing mail
Onetime Secret sends e-mail for account verification, password resets and the optional "send the link by e-mail" feature. The image ships with mail off (emailer: mode: disabled) and no credentials.
To turn mail on, replace the emailer: block at the end of /etc/onetime/config.yaml with your provider's settings. This example uses SMTP with STARTTLS on port 587:
emailer: mode: smtp from: secrets@example.com from_name: Secrets host: smtp.example.com port: 587 user: "<your-smtp-user>" pass: "<your-smtp-password>" auth: login tls: trueKeep the file's owner and mode (root:ots, 0640), then run sudo systemctl restart onetimesecret. Allow the outgoing port in your security group or network ACLs.
Onetime Secret also supports Amazon SES, SendGrid and other providers. See the upstream configuration reference in /opt/onetimesecret/app/etc/defaults/config.defaults.yaml.
Use your own domain and certificate
Point a DNS record (for example secrets.example.com) at the instance, then:
sudo onetimesecret-set-domain secrets.example.comnginx then serves that name. The instance's EC2 name and addresses keep working. Onetime Secret uses the new name for the links and e-mails it generates. If the certificate is still the SecureAnyCloud self-signed one, a new self-signed certificate is issued for the new name.
To use a certificate from a certificate authority, replace /etc/ssl/onetimesecret/onetimesecret.crt (full chain) and /etc/ssl/onetimesecret/onetimesecret.key (mode 0600, root). Then run sudo systemctl reload nginx. onetimesecret-set-domain never overwrites a certificate that is not SecureAnyCloud's own.
Check the current name and fingerprint with:
sudo onetimesecret-set-domain --showname: ec2-52-55-145-0.compute-1.amazonaws.com URL: https://ec2-52-55-145-0.compute-1.amazonaws.com/ also served: ec2-52-55-145-0.compute-1.amazonaws.com 52.55.145.0 172.31.93.253 certificate: SHA-256 DC:64:7E:6B:...The store, its password and encryption at rest
Valkey keeps Onetime Secret's data in /var/lib/valkey with persistence on (an append-only file written every second plus snapshots), so accounts and unread secrets survive a reboot. Onetime Secret encrypts secret contents before they reach Valkey.
The store is capped at 1 GB and refuses new secrets when full. It never silently drops a secret nobody has read yet. Check the store and its configuration with:
sudo onetimesecret-valkey-password check
sudo grep -vE '^(#|$|user )' /etc/valkey/sac-onetimesecret.confVALKEY_PASSWORD_OK auth=PONG anonymous=NOAUTH wrong=WRONGPASS config-set=NOPERM appendonly=yes bind 127.0.0.1 -::1 protected-mode yes port 6379 appendonly yes appendfsync everysec rdbcompression no maxmemory 1gb maxmemory-policy noeviction tls-port 0 cluster-enabled noThe store password is generated on each instance's first boot and kept in /etc/onetime/config.yaml. Valkey's file holds only its SHA-256.
To change the password, run sudo onetimesecret-valkey-password rotate. The command restarts Valkey and Onetime Secret. Secrets and accounts are kept.
Never change site.secret in /etc/onetime/config.yaml on a running service. Onetime Secret derives the encryption key of every stored secret from it, so changing it makes every unread secret unreadable. If you back up the store, back up /etc/onetime/config.yaml together with /var/lib/valkey.
Security notes
- Rate limits. nginx keys its limits on the real TCP peer address. A forged
X-Forwarded-Forheader neither changes the address nor appears in the log. The access log never records secret or receipt keys:
sudo grep -E 'limit_req_zone' /etc/nginx/conf.d/sac-onetimesecret-maps.conflimit_req_zone $binary_remote_addr zone=ots_all:10m rate=20r/s; limit_req_zone $ots_write_key zone=ots_write:10m rate=30r/m; limit_req_zone $ots_write_key zone=ots_login:10m rate=6r/m;- Metadata service. The AMI requires IMDSv2, and Onetime Secret, nginx, Valkey, the command-line tool and the first-boot service are denied the instance metadata addresses by systemd:
systemctl show -p IPAddressDeny onetimesecret nginx valkey-server onetimesecret-firstbootIPAddressDeny=::ffff:169.254.169.254/128 169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128 IPAddressDeny=::ffff:169.254.169.254/128 169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128 IPAddressDeny=::ffff:169.254.169.254/128 169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128 IPAddressDeny=::ffff:169.254.169.254/128 169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128- Logs. Onetime Secret's own log (the systemd journal,
journalctl -u onetimesecret) records requests with secret identifiers redacted. The image sets its framework loggers to warnings only, so identifiers of live secrets are not kept.
- Valkey advisories. Valkey 7.2.13 comes from the Ubuntu 24.04
universearchive (Ubuntu 24.04 has no Redis-compatible server inmain). Ubuntu publishes the fixes in USN-8771-1 (CVE-2026-56684 and CVE-2026-63639, both rated medium) only through Ubuntu Pro (ESM Apps). Neither is reachable on this image. CVE-2026-56684 needs Valkey's own TLS listener, which is never enabled here (tls-port 0; nginx terminates TLS for the web app). CVE-2026-63639 needs a client that may runRESTORE, which the image's only Valkey account may not. Valkey is also reachable only from the instance itself, with the per-instance password. If you attach the instance to an Ubuntu Pro subscription, unattended upgrades install the fixed package automatically.
- Ruby. Ruby 3.4.10 is the exact version Onetime Secret 0.26.19 requires. Ruby 3.4.12 (2026-10-23) fixes memory-safety bugs in compiling attacker-supplied regular expressions (behind GitLab's CVE-2026-89078, -93577, -97229 and -110264). Onetime Secret never compiles a regular expression from request data. Onetime Secret does not use Ruby 3.4.10's bundled resolv library (CVE-2026-80212, CVE-2026-80213), because its e-mail validation is a pattern check. SecureAnyCloud publishes a new version of this AMI when Onetime Secret moves to a newer Ruby.
- Upgrades. Ubuntu security updates (including nginx and Valkey) install automatically. SecureAnyCloud publishes new versions of this AMI for new Onetime Secret releases.
Licenses and source
Onetime Secret is MIT licensed. The image ships its license, the exact source reference and the build record:
cat /usr/share/doc/sac-onetimesecret/SOURCE.txt sed -n '1,4p' /usr/share/doc/sac-onetimesecret/BUILD-INFOsac Onetime Secret 0.26.19 appliance - source Onetime Secret (MIT licence, LICENSE-onetimesecret-MIT.txt, Copyright (c) 2012-ongoing delano, contributors): https://github.com/onetimesecret/onetimesecret/tree/v0.26.19 (commit e98a88c9b5832bbae249f38885aa28bd78a5db4d) Built unmodified from that tag, with upstream's own build steps (its Dockerfile): the gems from Gemfile.lock in this directory (upstream's lock with two in-range security raises: rack 3.2.7 -> 3.2.8 and sanitize 7.0.0 -> 7.0.1), the frontend from pnpm-lock.yaml + pnpm-workspace.yaml in this directory (upstream's lock with same-major security overrides: dompurify 3.4.17, vue-i18n / @intlify 11.4.14, follow-redirects 1.16.2). Test suites (spec/, tests/) and developer tooling are not shipped. Build details: BUILD-INFO; the gem list: gems.txt. Ruby 3.4.10 (Ruby licence or BSD-2-Clause: LICENSE-ruby-COPYING.txt, LICENSE-ruby-BSDL.txt) is built from https://cache.ruby-lang.org/pub/ruby/3.4/ruby-3.4.10.tar.gz into /opt/onetimesecret/ruby. Each gem's licence is in its own directory under /opt/onetimesecret/app/vendor/bundle/ruby/3.4.0/gems/. Valkey (BSD-3-Clause) and nginx (BSD-2-Clause) are the Ubuntu 24.04 packages: /usr/share/doc/<package>/copyright. Onetime Secret is the name of its open-source project; sac is not affiliated with or endorsed by it or by onetimesecret.com. sac build of Onetime Secret 0.26.19 (AWS bundle member onetimesecret-aws) source: github.com/onetimesecret/onetimesecret tag v0.26.19 commit e98a88c9b5832bbae249f38885aa28bd78a5db4d (tag tarball sha256 ef398bfbf063db1fa4ea6a9c5fcb7f90423564800840d563f19f1bda0da11d0a) licence: MIT (LICENSE.txt sha256 73fc32bac6ec19ddfc5986b9b740f05d988b15f81ccef65f91b4fc7349dd904d) ruby: 3.4.10 from https://cache.ruby-lang.org/pub/ruby/3.4/ruby-3.4.10.tar.gz (sha256 ecee2d072a14f2d14347dd56dfd8fe5c3130abf5117bfaacbda0f4ef9cc429ec), static libruby, prefix /opt/onetimesecret/rubySecureAnyCloud builds Onetime Secret from the upstream tag with upstream's own build steps, on a separate build host. The image never contains a compiler, Node.js or a package cache.
The only changes are in the dependency lock files. They are two in-range Ruby gem updates (rack 3.2.8 and sanitize 7.0.1) and same-major frontend updates (DOMPurify 3.4.17, vue-i18n 11.4.14, follow-redirects 1.16.2) that fix every advisory reported against the shipped dependencies. Test suites and developer tools are not shipped.
All product and company names are trademarks or registered trademarks of their respective holders. SecureAnyCloud is not affiliated with or endorsed by them, or by onetimesecret.com.
Support
SecureAnyCloud supports this image. Email sales@secureanycloud.com.
See the identity, secrets and PKI listing guide for the other versions in this listing.
Deploy on AWS
Launch Onetime Secret 0.26.19 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com