Cryptgeon
Overview
Cryptgeon is an open source service for sharing a secret once. You paste a password, an API key, a recovery phrase or a file, choose how many times it may be opened or how long it may live, and hand over a single link. The moment the recipient opens it, the note is destroyed.
Unlike a paste bin, Cryptgeon means that your own server cannot read what it stores. Each note is encrypted in the sender's browser, and the decryption key is placed in the fragment of the share link, after the #. Browsers never transmit the fragment, so the key never reaches your VM. What arrives is ciphertext and nothing else.
This SecureAnyCloud image builds Cryptgeon 2.9.3 from the pinned upstream source and runs it as a hardened systemd service behind nginx. It locks the in-memory note store with a password that is unique to your VM and generated on first boot.
What you get:
- Cryptgeon 2.9.3, built from upstream commit
40f3559, installed at/opt/cryptgeon - nginx on port
80as the only listener reachable from outside the VM - The application bound to
127.0.0.1:8000and Redis bound to127.0.0.1:6379, neither reachable off the VM - A 48-character Redis password generated on first boot, before anything opens a port
- No note persistence at all: the store runs in RAM with snapshots and append-only logging disabled
- Note limits set to bounded, documented values: 4 MiB per note, up to 100 views, up to 360 minutes
cryptgeon.servicerunning as the unprivilegedcryptgeonuser- 24/7 SecureAnyCloud support
Prerequisites
You need an active Azure subscription, an SSH key, and a VNet with a subnet.
Standard_B2s is the recommended size. Cryptgeon is light, and its memory use is bounded by the note store cap rather than by traffic.
Add an inbound network security group (NSG) rule for TCP 80 from wherever your users are. Add 443 once you have installed a certificate. Do not open ports 8000 or 6379. They are bound to loopback and are not meant to be reachable.
HTTPS is required for real use. Cryptgeon encrypts in the browser using the Web Crypto API, and browsers only expose that API on a secure origin. Over plain http:// to an IP address, the encryption cannot run, so the page will not create notes.
Step 6 covers installing a certificate. Until you do, reach the site from your workstation at http://localhost through an SSH tunnel, which browsers treat as a secure origin.
Deploy and connect
Deploy the image from the Azure Marketplace, then connect over SSH as azureuser:
ssh azureuser@<vm-ip>Confirm that all three services came up, check the version the running binary reports, and confirm the listener layout:
sudo systemctl is-active cryptgeon redis-server nginx curl -s http://127.0.0.1/api/status | jq '{version, max_size, max_views, max_expiration}' ss -tln | grep -E ':(80|8000|6379) ' | awk '{print $1" "$4}'cryptgeon.service carries a fail-closed pre-start gate, so seeing it active is itself the proof that the note store is password protected.
The output shows 2.9.3 and a max_size of 4194304 bytes. It also shows exactly one publicly bound listener on :80, with 8000 and 6379 on 127.0.0.1.
Review the per-VM secrets
Every VM built from this image generates its own note store password on first boot. Nothing is shared between VMs, and nothing is baked into the image. Read the credentials file:
sudo sed -n '1,32p' /stage/scripts/cryptgeon-credentials.log
sudo stat -c '%a %U:%G %n' /stage/scripts/cryptgeon-credentials.log /etc/cryptgeon/cryptgeon.envThe file is mode 0600, owned by root, and it opens with the complete secret inventory for this product. The inventory is deliberately short and worth reading once:
REDIS_PASSWORDis the only secret Cryptgeon uses. It protects the note store, which holds every note's ciphertext.- There is no signing, session or JWT secret. The entire configuration surface of the Cryptgeon backend is two source files, and the only secret-bearing variable in either is the Redis connection string. There is no server-side key material to rotate.
- There is no administrator or user account. Cryptgeon is anonymous by design: no accounts, no login, no admin console. No default credential exists, so none ships enabled.
- Note encryption keys never reach the server. They are generated in the visitor's browser and live in the URL fragment.
- The TLS certificate is yours to supply. See Step 6.
Watch a note self-destruct
This is the whole product in three commands. Create a note limited to one view, read it, then try to read it again. Reading is a DELETE, because reading is the destruction.
NOTE_ID=$(curl -sS -X POST http://127.0.0.1/api/notes \
-H 'Content-Type: application/json' \
-d '{"meta":"demo","contents":"launch-codes-42","views":1}' | jq -r .id)
echo "created note id: ${NOTE_ID:0:24}..."
echo "first read -> $(curl -sS -X DELETE http://127.0.0.1/api/notes/$NOTE_ID | jq -r .contents)"
echo "second read -> HTTP $(curl -sS -o /dev/null -w '%{http_code}' http://127.0.0.1/api/notes/$NOTE_ID) (note destroyed)"The first read returns the payload. The second returns 404, because the note is gone from the store and cannot be recovered by anyone, including you.
These commands send plaintext, because curl is not a browser and cannot run the client-side encryption. That is fine for a health check, but it is not how the product is meant to be used. Real notes are encrypted in the browser, as Step 5 shows.
Confirm the note store is locked down
Redis holds every note's ciphertext, so it is the surface that matters most on this VM. Read the password from the credentials file rather than pasting it around, then probe the store the way an attacker would:
REDIS_PASSWORD="$(sudo grep '^REDIS_PASSWORD=' /stage/scripts/cryptgeon-credentials.log | cut -d= -f2-)" echo "anonymous PING -> $(redis-cli ping </dev/null 2>&1 | head -1)" echo "anonymous KEYS * -> $(redis-cli keys '*' </dev/null 2>&1 | head -1)" echo "wrong password -> $(redis-cli -a wrong --no-auth-warning ping </dev/null 2>&1 | head -1)" echo "per-VM password -> $(redis-cli -a "$REDIS_PASSWORD" --no-auth-warning ping </dev/null 2>/dev/null)" echo "rdb snapshots -> $(redis-cli -a "$REDIS_PASSWORD" --no-auth-warning config get save </dev/null 2>/dev/null | tail -1 | sed 's/^$/(none)/')" echo "append-only log -> $(redis-cli -a "$REDIS_PASSWORD" --no-auth-warning config get appendonly </dev/null 2>/dev/null | tail -1)" echo "note data on disk -> $(ls /var/lib/redis/*.rdb /var/lib/redis/appendonlydir 2>/dev/null | wc -l) file(s)"Anonymous commands are refused with NOAUTH, a wrong password is refused with WRONGPASS, and only the per-VM password returns PONG.
Snapshots report (none) and the append-only log reports no. Together, these prove that no note ciphertext is ever written to this VM's disk. Nothing survives a reboot, and nothing can be recovered from a disk snapshot or a backup.
Use the web interface
Open the site in a browser. Until you have installed a certificate in Step 6, use an SSH tunnel from your workstation. Browsers only enable the Web Crypto API on a secure origin, and localhost counts as one:
ssh -L 8080:127.0.0.1:80 azureuser@<vm-ip>Then browse to http://localhost:8080 and type your secret into the note box. The footer shows the current limit, and the line underneath describes what will happen to the note. Open advanced to set a view count or a time limit, or switch to file to upload a file instead.
Press create. The note is encrypted in your browser first, and only then is the ciphertext sent to the server. You get back a share link and a QR code. Everything after the # in that link is the decryption key. It never leaves your browser, so anyone who can read your server's memory or its note store still cannot read the note.
Send the link to the recipient. When they open it, they see a confirmation prompt. Pressing show note fetches the ciphertext, decrypts it in their browser, and deletes it from the server in the same operation.
If anyone opens the same link a second time, there is nothing left to fetch.
Install a TLS certificate
Cryptgeon depends on the browser's Web Crypto API, which browsers disable on insecure origins. Without HTTPS, your users cannot create or read notes at all except over a localhost tunnel. Point a DNS record at the VM, open port 443 in your network security group, then issue a certificate.
Replace <your-domain> and <your-email> in the following commands with your own values before you run them.
sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d <your-domain> -m <your-email> --agree-tos --redirect --non-interactiveCertbot rewrites the shipped nginx server block in place, keeping the client_max_body_size and the proxy settings, and installs a renewal timer. Afterwards, confirm the configuration with sudo nginx -t and reload nginx with sudo systemctl reload nginx.
Tune the limits
All configuration lives in one file. Every value the Cryptgeon backend reads is listed there, and the shipped defaults are bounded rather than permissive:
sudo grep -vE '^\s*#|^\s*$' /etc/cryptgeon/cryptgeon.env | sed 's/^REDIS=.*/REDIS=redis:\/\/:<redacted>@127.0.0.1:6379\//'SIZE_LIMIT: shipped value 4194304 (4 MiB). The largest request body. The UI shows roughly 2.96 MiB of usable note, because encoding adds about 35 percent. Upstream allows up to 512 MiB, but an unbounded note size on a small VM is an easy denial of service.
MAX_VIEWS: shipped value 100. The highest view count a note may request.
MAX_EXPIRATION: shipped value 360. The longest lifetime in minutes.
ALLOW_ADVANCED: shipped value true. Lets users choose views and expiry. Set it to false to force every note to be one view only.
ALLOW_FILES: shipped value true. Allows file uploads as well as text.
ID_LENGTH: shipped value 32. The note id length in bytes. Lower it for shorter links. It does not affect encryption strength.
LISTEN_ADDR: shipped value 127.0.0.1:8000. Loopback only, because nginx is the public listener. Do not change this.
After editing, restart the service and confirm the new values are live:
sudo systemctl restart cryptgeon.service for i in $(seq 1 30); do curl -sf -o /dev/null http://127.0.0.1/api/live && break; sleep 1; done curl -s http://127.0.0.1/api/status | jq '{max_size, max_views, max_expiration, allow_advanced, allow_files}'The wait loop matters. The service takes a moment to rebind after a restart, and querying it too early returns nothing to parse.
The note store is capped at 512 MB with a least-recently-used eviction policy, which is why the UI warns that notes are held in RAM and the oldest may be removed. If you expect heavy use, raise maxmemory in /etc/redis/sac-cryptgeon.conf, keeping it comfortably below the VM's total memory.
Components
Cryptgeon binary: /opt/cryptgeon/cryptgeon
Web frontend: /opt/cryptgeon/frontend
Configuration: /etc/cryptgeon/cryptgeon.env (mode 0640, root:cryptgeon)
Per-VM secrets: /stage/scripts/cryptgeon-credentials.log (mode 0600, root:root)
Note store overrides: /etc/redis/sac-cryptgeon.conf
nginx site: /etc/nginx/sites-available/cryptgeon
Fail-closed pre-start gate: /usr/local/sbin/cryptgeon-require-redis-auth.sh
First-boot script: /usr/local/sbin/cryptgeon-firstboot.sh
Service units: cryptgeon.service, cryptgeon-firstboot.service
Logs are in the journal:
sudo journalctl -u cryptgeon.service -n 20 --no-pagerSecurity notes
- The server cannot read your notes. Encryption happens in the browser and the key stays in the URL fragment. This is structural, not a policy setting, and it holds even against someone with root on the VM.
- Nothing is written to disk. The note store keeps everything in memory with persistence disabled, so no note reaches the filesystem, a snapshot or a backup.
- The note store is loopback-bound and password protected on every boot. The password is generated per VM at first boot. The application and nginx are both gated on a bootstrap marker that only appears after that password exists, so nothing serves a request before the store is locked. A pre-start check independently refuses to start Cryptgeon against an unauthenticated store.
- There are no accounts to protect, and no admin console to expose. Anyone who can reach the page can create a note. If that is not what you want, restrict access at the network security group or put your own authenticating proxy in front of port 80.
- A share link is the secret. Treat it like the credential it carries. Send it over a different channel from the one that will use it, and prefer short view counts and expiry times.
- Security updates continue to install automatically through
unattended-upgrades.
Support and licensing
Cryptgeon is licensed under the MIT licence. There is no per-seat, per-CPU or per-site fee for the software itself. The SecureAnyCloud charge covers packaging, security patching, image maintenance and 24/7 expert support.
Email sales@secureanycloud.com for help with certificates, limits, branding, network rules or upgrades. Critical issues receive a one-hour average response time.
Deploy on Azure
Find this image on the Azure Marketplace and browse the rest of the catalogue at secureanycloud.com/products.
Deploy on Azure
Launch Cryptgeon on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com