# Onetime Secret: a self-hosted single-use link service you run yourself

> Onetime Secret turns a password or note into a URL that can be opened once. The Ruby project ships a Docker quick start, a simple Compose stack, and a full stack with PostgreSQL, RabbitMQ and MFA. The trade-off is that losing the root SECRET makes existing secrets unreadable.

**onetimesecret/onetimesecret** — Keep passwords and other sensitive information out of your chat logs and inboxes.

- Repository: https://github.com/onetimesecret/onetimesecret
- Website: https://onetimesecret.com
- Stars: 2,947 · Forks: 454
- Language: Ruby
- License: MIT
- Published: 2026-09-24 · Updated: 2026-09-24 · Language: en
- Canonical page: https://hysenlabs.com/projects/onetimesecret-onetimesecret

## The problem Onetime Secret solves, and who ends up running it

A password pasted into a chat window or an email body does not disappear when the conversation ends. It stays in the recipient's inbox, in the sender's sent folder, in backups, and possibly in a provider's index. Onetime Secret's answer is a link that can be viewed only once: a single-use URL. The README describes it exactly that way, and the project states the goal as keeping passwords and other sensitive information out of inboxes and chat logs.

The people who get the most out of the repository are not the ones using the hosted service at onetimesecret.com. They are the ones who need the link to be generated on their own host, under their own domain, with their own retention rules. That usually means a platform or infrastructure engineer at a company that already forbids pasting credentials into Slack or email, or a small team that wants a self-hosted URL shortener for secrets rather than for marketing links. The distinction matters because the repository is a full application, not a library you embed: it ships a Dockerfile, Compose files, a Ruby application, a web front end, and an admin area.

## How a one-time link works in this codebase

The mechanism visible in the repository is a server-side store plus a single-use token. Redis is the default backing store: the Docker quick start starts a Redis container, and .env.example sets REDIS_URL to redis://127.0.0.1:6379/0 for bare-metal installs. The application receives the secret, stores it, and hands back a URL. When the recipient opens that URL, the secret is revealed and the record is consumed, so a second visit does not return the same content.

The cryptographic material is derived from one root value. The .env.example file states that SECRET is the single root secret and that all other cryptographic material is derived from it via HKDF (RFC 5869). A second value, ACCOUNT_ID_SECRET, obfuscates numeric account IDs in email-verification links and remember-me cookies, and the same file says it is required for full-auth production. Both are generated by bin/setup --init.

That design has a direct consequence for operations. The SECRET is not a per-secret key that you can rotate casually; it is the root of the derivation, so the README's warning that losing it is not recoverable is consistent with the architecture rather than a caution added for effect. Redis holds the pending secrets, and the SECRET holds the ability to read them. Back up both, or accept that a rebuild is a reset.

The web layer is a Ruby application served by Puma, with a Vite-built front end and a separate admin build target visible in package.json. Site identity is configuration: HOST sets the public hostname including a non-standard port, and SSL controls the scheme of generated links and, together with the session settings, the Secure flag on the session cookie.

## Docker quick start: from zero to a working link on port 3000

The README's quick start is three commands. First, start Redis on port 6379. Second, generate a persistent root secret and store it in a file with restrictive permissions. Third, run the application image with that secret, the Redis URL, the host, and SSL turned off for local HTTP. The image tag in the README is onetimesecret/onetimesecret:v0.26.13.

```bash
docker run -p 6379:6379 -d redis:trixie
openssl rand -hex 32 > .ots_secret && chmod 600 .ots_secret
docker run -p 3000:3000 -d \
  --name onetimesecret \
  --add-host=host.docker.internal:host-gateway \
  -e REDIS_URL=redis://host.docker.internal:6379/0 \
  -e SECRET="$(cat .ots_secret)" \
  -e HOST=localhost:3000 \
  -e SSL=false \
  onetimesecret/onetimesecret:v0.26.13
```

After that, open http://localhost:3000. The next step is creating an admin account. The README calls this role colonel, and notes that the command prints a generated password and verifies the account immediately.

```bash
docker exec onetimesecret bin/ots customers create me@example.com --role colonel
```

The README then flags the operational rule in a callout: losing your SECRET key is not recoverable, and it makes existing secrets unreadable. Treat .ots_secret as production data from the first minute, not as a local convenience file. If you prefer Compose over individual docker run commands, the repository's docker-compose.yml includes docker/compose/docker-compose.simple.yml by default and documents swapping in docker/compose/docker-compose.full.yml for the production stack with Caddy TLS, RabbitMQ, workers and a scheduler. The Compose file's own instructions are to copy .env.example to .env and run docker compose up.

```bash
[ -f .env ] || cp -p .env.example .env
docker compose up
```

For a bare-metal or tarball install, .env.example gives a different path: run bin/setup --init to copy the file to .env and generate secrets, source the environment, then start Puma with bundle exec puma -C etc/puma.rb.

## Where Onetime Secret stops being the right tool

The first limitation is stated by the project itself: losing the SECRET key is not recoverable. If your threat model includes an operator who must be able to decrypt an old secret after an incident, this application is the wrong shape. There is no documented recovery path, and the README does not document rollback or key rotation for the root secret.

The second limitation follows from the storage layer. Pending secrets live in Redis by default. If that Redis instance is ephemeral, or if its persistence is not configured, a restart can take unviewed secrets with it. The Dockerfile's own example starts a Valkey container with a named volume, which suggests persistence is expected in a real deployment, but the quick start in the README does not attach a volume to the Redis container at all. That is fine for a demo and wrong for anything you depend on.

The third limitation is scope. This is a single-use link service, not a password manager and not an end-to-end encrypted messenger. Nothing in the repository suggests the recipient authenticates before viewing beyond the link token itself, so anyone who obtains the URL before the intended recipient can consume it. That is inherent to the design, not a defect, but it means the link must travel over a channel you trust.

Finally, the README is explicit that the project was developed with help from AI tools for architecture design, code generation and documentation, with maintainers stating they remain responsible for design decisions and the final code. That is a transparency statement, not a quality claim in either direction, but it is information a reviewer should have.

## Onetime Secret compared with a general-purpose secrets manager

The obvious alternative category is a secrets manager such as HashiCorp Vault or a cloud provider's parameter store. The difference in approach is fundamental. A secrets manager keeps a durable, access-controlled record and expects the secret to be retrieved many times by authorized identities. Onetime Secret keeps a record that is meant to be consumed exactly once and then be gone. If your requirement is a rotation policy, an audit trail of every read, and programmatic access for services, a secrets manager is the right tool and Onetime Secret is not.

The closer alternative is another single-use link service. The repository itself points to docs/similar-services.md under a See also heading, so the maintainers keep a list rather than pretending the category is empty. The practical differences to weigh are deployment shape and feature surface: whether the service is only available as a hosted product, whether it supports self-hosting at all, and whether it offers the full-authentication stack that Onetime Secret's self-hosting guide describes with PostgreSQL, RabbitMQ, MFA and WebAuthn. If you only need a link you can send once and you are content with someone else's servers, the hosted service at onetimesecret.com removes the entire operational burden described above.

## Maintenance, upgrades and what the MIT licence means here

The repository is not archived, and the last push was on 2026-09-23. Releases are frequent rather than annual: v0.26.13 on 2026-09-23, v0.26.12 on 2026-09-10, and v0.26.11 on 2026-09-07. That cadence is a maintenance cost as well as a signal. The README pins onetimesecret/onetimesecret:v0.26.13 in its quick start, so an upgrade is a deliberate tag change rather than a floating latest, and the CHANGELOG.rst plus the changelog.d directory are where release notes accumulate.

The upgrade surface is wider than a single container. The full stack adds Caddy for TLS, RabbitMQ, workers and a scheduler, and the simple stack is app plus Valkey. An upgrade therefore touches the application image, the Compose include, and any configuration you have in .env or the YAML defaults under etc/defaults. Because the root SECRET derives the rest of the cryptographic material, it must survive every upgrade; the README's warning applies as much to a version bump as to a disk failure.

The licence is MIT, per LICENSE.txt. In practical terms that is a permissive licence with few obligations beyond retaining the notice, which makes it straightforward to run internally or modify. This is not legal advice, and if you plan to redistribute a modified build or offer it as a service, read LICENSE.txt yourself and check whether the project's trademark or branding is addressed separately, since the repository contains branding scripts with presets for several names.

## Conclusion

Adopt Onetime Secret if you need single-use links on infrastructure you control and you can store the root SECRET and the Redis data safely. Do not adopt it if you expect lost secrets to be recoverable, because the README states that losing the SECRET key is not recoverable. Before rolling it out, verify that your Redis persistence and your backup of the SECRET file actually restore, and confirm whether you need the simple stack or the full stack with PostgreSQL, RabbitMQ, MFA and WebAuthn.

## FAQ

### What is Onetime Secret?

It is an open source Ruby application that turns a password or other sensitive text into a single-use URL, described in the README as a link that can be viewed only once. You can use the hosted service at onetimesecret.com or run the software yourself, including a Docker quick start on port 3000.

### How does one time secret work?

The application stores the secret server-side, by default in Redis, and returns a URL. When the recipient opens that URL the secret is revealed and the record is consumed, so a second visit does not return the same content. All other cryptographic material is derived from the single root SECRET value via HKDF.

### how to use onetimesecret

The README's quick start runs Redis on port 6379, generates a root secret with openssl rand -hex 32, then starts the onetimesecret/onetimesecret image with REDIS_URL, SECRET, HOST and SSL set. You then open http://localhost:3000 and create an admin account with bin/ots customers create me@example.com --role colonel.

### Is onetimesecret free?

The repository is published under the MIT licence, so you can run it yourself without paying for the software. The repository does not describe the pricing of the hosted service at onetimesecret.com, so check that separately if you intend to use it rather than self-host.

### Is one-time password safe?

Onetime Secret is a single-use link service, not a one-time password generator, so the two are different things. Within its own design, the link is consumed on first view, which means anyone who obtains the URL before the intended recipient can view it, so the link itself must travel over a channel you trust.

## Sources

- [License: MIT](https://github.com/onetimesecret/onetimesecret/blob/main/LICENSE)
- [onetimesecret/onetimesecret on GitHub](https://github.com/onetimesecret/onetimesecret)
- [Project website](https://onetimesecret.com)
- [README](https://github.com/onetimesecret/onetimesecret/blob/main/README.md)
- [Releases](https://github.com/onetimesecret/onetimesecret/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/onetimesecret-onetimesecret
