Deployment guide

Cryptgeon 2.9.3

Overview

Cryptgeon is an open-source service for sharing a secret once. Paste a password, an API key or a file, choose how many times it may be opened or how long it may live, and send the link. Each note is encrypted in the sender's browser (AES-GCM, with a key generated on that device). The key sits in the part of the link after the #, which browsers never send to a server, so the instance only ever receives and stores ciphertext. When the last permitted view happens or the time limit runs out, the note is deleted.

This SecureAnyCloud image is a hardened, ready-to-use Cryptgeon 2.9.3 server on Ubuntu 24.04 on Amazon EC2. It is one version of the SecureAnyCloud identity, secrets and PKI listing on AWS Marketplace. You select it as the software version when you subscribe and launch.

What you get:

  • nginx in front, Cryptgeon behind it. nginx is the only public listener (ports 80 and 443). Cryptgeon listens on 127.0.0.1:8000, the loopback address only.
  • HTTPS from the first boot. Browsers allow the Web Crypto functions Cryptgeon needs only on a secure (HTTPS) page. The image therefore serves 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. First boot sets the instance's name from its own EC2 public DNS name. You can change it to your own domain.
  • Notes in memory only. Valkey, the Redis-compatible key-value store from Ubuntu 24.04, stores the notes with snapshots and the append-only file switched off. A note never reaches a disk, a snapshot or a backup. Valkey listens on 127.0.0.1 and ::1 only.
  • A per-instance note store password, never in a process environment. First boot generates a random Valkey password. Cryptgeon reads it from a file that only its own service user can read. The password is never on a command line, in a systemd unit or in a process environment, and Valkey itself keeps only its SHA-256 hash. The one Valkey account may run only the commands Cryptgeon uses. Valkey does not start until first boot has created that account.
  • Abuse limits. Cryptgeon has no accounts, so anyone who can reach the address can create notes. That is how Cryptgeon is designed. The image bounds what one client can do: a note request is at most 4 MiB, at most 100 views and at most 360 minutes. nginx allows 12 new notes per minute per client address (with a burst of 20), and a page on another website cannot create or read notes here.
  • Real client addresses, fixed host name. The rate limits use the real TCP peer address, never a header a client can forge. Requests for any host name other than the instance's configured names are dropped.
  • Browser hardening. A Content-Security-Policy allows only the application's own scripts (the one inline start-up script is pinned by its hash), so injected script cannot read a note's key from the page. Framing is refused, and the server sends no referrer. The nginx access log records /api/notes/:id instead of note ids.
  • A locked-down host. TLS 1.2 and 1.3 only. Cryptgeon, nginx, Valkey and the first-boot service are blocked from the instance metadata service, and the AMI requires IMDSv2.
  • Built from source and verified. Ubuntu 24.04 LTS is fully patched at build time, with unattended security upgrades switched on. Step 12 describes exactly how the image is built.

The image contains Cryptgeon 2.9.3 (a SecureAnyCloud build of the upstream v2.9.3 tag, /usr/local/bin/cryptgeon, with its web app in /usr/share/cryptgeon/frontend), Valkey 7.2.13 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. It should allow TCP 443 (the web app) and TCP 80 (redirect 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 notes, so do not open it to 0.0.0.0/0 unless you want a public service. Do not open 8000 or 6379, because Cryptgeon 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.
1

Launch the instance

In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick Cryptgeon 2.9.3 on Ubuntu 24.04 as the software version, select your Region, and choose Launch through EC2. Choose m5.large, your key pair and a security group as described above.

Alternatively, 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=cryptgeon}]'

First boot takes a few seconds after the instance is running.

To have the instance use your own name from the start, put the name in /etc/sac/cryptgeon-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.

2

Connect to your instance

Connect over SSH as the ubuntu user. This is the default user on Ubuntu 24.04.

ssh -i <your-key.pem> ubuntu@<public-ip>
3

Check the services

systemctl is-active cryptgeon-firstboot valkey-server cryptgeon nginx
active active active active

Only SSH, HTTP and HTTPS are public. Cryptgeon and Valkey listen on the loopback address. Confirm the listeners:

sudo ss -tln | grep -E ':(22|80|443|6379|8000) '
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 127.0.0.1:6379 0.0.0.0:* LISTEN 0 511 0.0.0.0:80 0.0.0.0:* LISTEN 0 128 127.0.0.1:8000 0.0.0.0:* LISTEN 0 511 [::1]:6379 [::]:* LISTEN 0 511 [::]:443 [::]:* LISTEN 0 4096 [::]:22 [::]:* LISTEN 0 511 [::]:80 [::]:*

Run the local health check, which is answered only on the loopback address. It confirms that Cryptgeon can reach its note store:

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/api/live
200
4

Read the address and the certificate fingerprint

sudo cat /root/cryptgeon-credentials.txt

The file (root only, mode 0600) gives this instance's address, the SHA-256 fingerprint of its certificate and the note limits. There is no administrator password, because Cryptgeon has no accounts and no admin console.

# Cryptgeon 2.9.3 - written on first boot by cryptgeon-firstboot.service. Unique to this instance. CRYPTGEON_URL=https://ec2-100-53-91-212.compute-1.amazonaws.com/ CRYPTGEON_TLS_SHA256=FE:DE:21:21:...:(64 hex digits in pairs) CRYPTGEON_SIZE_LIMIT_BYTES=4194304 CRYPTGEON_MAX_VIEWS=100 CRYPTGEON_MAX_EXPIRATION_MINUTES=360
# There are no accounts: anyone who can reach CRYPTGEON_URL can create notes. Restrict the security group's # 80/443 rules to the networks that should use it (your office, VPN or VPC CIDR). ...

The certificate is self-signed and unique to your instance, so your browser shows a warning. Open the certificate details in the browser and check that its SHA-256 fingerprint matches CRYPTGEON_TLS_SHA256. Continue only if it matches.

Do not add the certificate to your operating system's trust store. For production, replace it with a certificate from a certificate authority (Step 9). The certificate is an end-entity certificate for this instance's names only:

openssl x509 -in /etc/ssl/cryptgeon/cryptgeon.crt -noout -subject -ext basicConstraints,keyUsage,extendedKeyUsage,subjectAltName
subject=CN = ec2-100-53-91-212.compute-1.amazonaws.com, O = sac X509v3 Subject Alternative Name: DNS:ec2-100-53-91-212.compute-1.amazonaws.com, IP Address:100.53.91.212, IP Address:172.31.93.75 X509v3 Basic Constraints: critical CA:FALSE X509v3 Key Usage: critical Digital Signature, Key Encipherment X509v3 Extended Key Usage: TLS Web Server Authentication
5

Share a secret

Browse to the CRYPTGEON_URL address. Type or paste the secret.

Choose create. Your browser encrypts the note and uploads only the ciphertext. The page then shows the share link, also as a QR code. Send the link to the recipient over any channel.

The recipient opens the link and chooses show note. Their browser downloads the ciphertext, decrypts it with the key from the link and shows the secret.

The note is now gone. Opening the same link again shows that the note was not found.

By default, a note may be opened once. Switch on advanced to allow more views (up to 100), or to keep the note for a number of minutes instead (up to 360). You can also protect the note with your own password, which is then not part of the link.

6

Share a file

Switch on file, select one or more files and choose create. The browser encrypts the files like a text note. The recipient downloads and decrypts them.

A note request is limited to 4 MiB. After encryption and encoding, that means files of about 3 MB in total. The page shows the exact figure, max: 2.96 MiB. A larger file is refused.

7

Use the API

The web app talks to a small JSON API, which you can also call from scripts. The server never sees a key. Anything you send to POST /api/notes is stored as you send it, so encrypt it first. The Cryptgeon command-line client, npx cryptgeon, does this for you.

On the instance itself, call the API with the instance's own certificate as the trust anchor:

U=$(sudo sed -n 's/^CRYPTGEON_URL=//p' /root/cryptgeon-credentials.txt)
curl -s --cacert /etc/ssl/cryptgeon/cryptgeon.crt "${U}api/status"; echo
{"version":"2.9.3","max_size":4194304,"max_views":100,"max_expiration":360,"allow_advanced":true,"allow_files":true,"imprint_url":"","imprint_html":"","theme_image":"","theme_text":"","theme_page_title":"","theme_favicon":"","theme_new_note_notice":true,"theme_home_link":true}

Create a note that may be read once. Look at it without using the view (GET returns only its metadata). Read it (DELETE returns the contents and uses the view). Then try to read it again:

U=$(sudo sed -n 's/^CRYPTGEON_URL=//p' /root/cryptgeon-credentials.txt)
ID=$(curl -s --cacert /etc/ssl/cryptgeon/cryptgeon.crt -H 'Content-Type: application/json' \
  --data '{"meta":"{\"type\":\"text\"}","contents":"api-test","views":1}' "${U}api/notes" | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')
echo "created a note with id of ${#ID} characters" curl -s --cacert /etc/ssl/cryptgeon/cryptgeon.crt -o /dev/null -w 'preview: %{http_code}\n' "${U}api/notes/${ID}" curl -s --cacert /etc/ssl/cryptgeon/cryptgeon.crt -X DELETE -w '\nfirst read: %{http_code}\n' "${U}api/notes/${ID}" curl -s --cacert /etc/ssl/cryptgeon/cryptgeon.crt -o /dev/null -X DELETE -w 'second read: %{http_code}\n' "${U}api/notes/${ID}"
created a note with id of 64 characters preview: 200 {"meta":"{\"type\":\"text\"}","contents":"api-test"} first read: 200 second read: 404

Requests outside the limits are refused. So is a request made by a page on another website, because its Origin is not this instance:

U=$(sudo sed -n 's/^CRYPTGEON_URL=//p' /root/cryptgeon-credentials.txt)
curl -s --cacert /etc/ssl/cryptgeon/cryptgeon.crt -o /dev/null -w '%{http_code}\n' -H 'Content-Type: application/json' --data '{"meta":"x","contents":"x","views":101}' "${U}api/notes"
curl -s --cacert /etc/ssl/cryptgeon/cryptgeon.crt -o /dev/null -w '%{http_code}\n' -H 'Content-Type: application/json' -H 'Origin: https://evil.example' --data '{"meta":"x","contents":"x","views":1}' "${U}api/notes"
400 403
8

Limits and abuse protection

The limits Cryptgeon enforces are set in /etc/cryptgeon/cryptgeon.env:

sudo grep -E '^(LISTEN_ADDR|SIZE_LIMIT|MAX_VIEWS|MAX_EXPIRATION|ALLOW_ADVANCED|ALLOW_FILES)=' /etc/cryptgeon/cryptgeon.env
LISTEN_ADDR=127.0.0.1:8000 SIZE_LIMIT=4194304 MAX_VIEWS=100 MAX_EXPIRATION=360 ALLOW_ADVANCED=true ALLOW_FILES=true

SIZE_LIMIT is in bytes. ALLOW_FILES=false turns file notes off. ALLOW_ADVANCED=false makes every note a one-view note. After editing the file, run sudo systemctl restart cryptgeon.

If you raise SIZE_LIMIT above 4 MiB, also raise client_max_body_size (5m) in /usr/local/lib/sac/cryptgeon/nginx-site.tmpl. Then run sudo cryptgeon-set-domain --boot, followed by sudo systemctl reload nginx.

nginx limits how fast one client address can create notes and call the API. The address is the real TCP peer. A forged X-Forwarded-For header neither changes it nor appears in the log:

sudo grep -E 'limit_req_zone|rate=' /etc/nginx/conf.d/sac-cryptgeon-maps.conf
limit_req_zone $cg_create_key zone=cg_create:10m rate=12r/m; limit_req_zone $binary_remote_addr zone=cg_api:10m rate=20r/s;

A client over the limit gets HTTP 429 until its allowance refills. The access log records the note API without note ids. Check the log:

sudo tail -3 /var/log/nginx/access.log | sed -E 's/^[0-9.]+ /<client-ip> /'
<client-ip> - - [29/Oct/2026:10:05:04 +0000] "DELETE /api/notes/:id HTTP/2.0" 404 0 "curl/8.5.0" <client-ip> - - [29/Oct/2026:10:05:04 +0000] "POST /api/notes HTTP/2.0" 400 13 "curl/8.5.0" <client-ip> - - [29/Oct/2026:10:05:04 +0000] "POST /api/notes HTTP/2.0" 403 146 "curl/8.5.0"

Your security group decides who can create notes. Limit the 443 and 80 rules to the networks that should use the service. To put the service on a private address only, launch it in a private subnet (or remove the public IP) and allow 443 from your VPC CIDR or VPN range.

The note store and memory. Valkey keeps everything in RAM with a 512 MB cap. Notes with an expiry time may be removed early when the store is full. Notes limited by view count are never removed early. When the store is full of them, a new note is refused (the page says the note could not be created) until notes are read or expire.

A reboot or a Valkey restart deletes every unread note. Check the store with:

sudo cryptgeon-valkey-password check
sudo grep -E '^(bind|port|save|appendonly|maxmemory|maxmemory-policy|tls-port|cluster-enabled) ' /etc/valkey/sac-cryptgeon.conf
VALKEY_PASSWORD_OK auth=PONG anonymous=NOAUTH wrong=WRONGPASS save='' appendonly=no bind 127.0.0.1 -::1 port 6379 save "" appendonly no maxmemory 512mb maxmemory-policy volatile-ttl tls-port 0 cluster-enabled no

To change the cap, edit maxmemory in /usr/local/lib/sac/cryptgeon/valkey.conf.tmpl and run sudo cryptgeon-valkey-password rotate. The command re-renders the file with a new password and restarts Valkey and Cryptgeon, which deletes all unread notes.

9

Use your own domain and certificate

Point a DNS record (for example secrets.example.com) at the instance, then set the name on the instance:

sudo cryptgeon-set-domain secrets.example.com

nginx then serves that name. The instance's EC2 name and addresses keep working. If the certificate is still the SecureAnyCloud self-signed one, a new self-signed certificate is issued for the new name. Share links use the name the sender browsed to.

To use a certificate from a certificate authority, replace /etc/ssl/cryptgeon/cryptgeon.crt (full chain) and /etc/ssl/cryptgeon/cryptgeon.key (mode 0600, root), then run sudo systemctl reload nginx. cryptgeon-set-domain never overwrites a certificate that is not SecureAnyCloud's own.

Check the current name and fingerprint with sudo cryptgeon-set-domain --show.

10

The note store password

Each instance generates its Valkey password on first boot. Cryptgeon reads it from /etc/cryptgeon/.env (mode 0600, owned by the cryptgeon service user) with its own configuration loader, so the password is not in the service's environment. Valkey's file holds only its SHA-256:

sudo stat -c '%a %U %n' /etc/cryptgeon/.env /etc/valkey/sac-cryptgeon.conf
600 cryptgeon /etc/cryptgeon/.env 600 valkey /etc/valkey/sac-cryptgeon.conf

To change the password, run sudo cryptgeon-valkey-password rotate. Like any Valkey restart, this deletes all unread notes.

11

Security notes

  • Metadata service. The AMI requires IMDSv2, and systemd denies Cryptgeon, nginx, Valkey and the first-boot service access to the instance metadata addresses. Cryptgeon makes no outbound requests at all.
systemctl show -p IPAddressDeny cryptgeon nginx valkey-server cryptgeon-firstboot
IPAddressDeny=fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32 ::ffff:169.254.169.254/128 IPAddressDeny=fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32 ::ffff:169.254.169.254/128 IPAddressDeny=fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32 ::ffff:169.254.169.254/128 IPAddressDeny=fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32 ::ffff:169.254.169.254/128
  • Response headers. Every page is served with a Content-Security-Policy that allows only Cryptgeon's own scripts, plus X-Frame-Options: DENY, X-Content-Type-Options: nosniff and Referrer-Policy: no-referrer. API responses are not cacheable.
  • Valkey advisories. Valkey 7.2.13 comes from the Ubuntu 24.04 universe archive. 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 issue 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 run RESTORE, 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. Otherwise, the fix arrives with Ubuntu's next public Valkey update or the next SecureAnyCloud version of this AMI.
  • Upgrades. Ubuntu security updates (including nginx and Valkey) install automatically. Cryptgeon itself is a single binary. SecureAnyCloud publishes new versions of this AMI for new Cryptgeon releases.
12

Licenses and source

Cryptgeon is MIT licensed. The image ships its license, the exact source reference and the build record:

cat /usr/share/doc/sac-cryptgeon/SOURCE.txt sed -n '1,4p' /usr/share/doc/sac-cryptgeon/BUILD-INFO
sac Cryptgeon 2.9.3 appliance - source Cryptgeon (MIT licence, LICENSE-cryptgeon-MIT.txt, Copyright (c) 2021 Niccolo Borgioli): https://github.com/cupcakearmy/cryptgeon/tree/v2.9.3 (commit 40f35595337a6008afd41cd834b8eb4e8644f956) Built unmodified from that tag: the backend with Rust 1.99.1 against the dependency lock file Cargo.lock in this directory (semver-compatible security bumps only, listed in BUILD-INFO), the frontend with upstream's own pnpm-lock.yaml (unmodified). Build details: BUILD-INFO. Valkey (BSD-3-Clause) is the Ubuntu 24.04 valkey-server package: /usr/share/doc/valkey-server/copyright. Cryptgeon is a trademark of its respective owner; sac is not affiliated with or endorsed by it. Cryptgeon 2.9.3 - sac build of the pinned upstream tag (no source patch) source: https://github.com/cupcakearmy/cryptgeon.git tag v2.9.3 commit 40f35595337a6008afd41cd834b8eb4e8644f956 (MIT, LICENSE sha256 5e0d5cf72b0db671448dae3b495ce60c46308e74459119fcd0fc9637b85f01a9) toolchain: rustc 1.99.1 (58cf8f9c3 2026-10-13) (rustup 1.29.2, profile minimal); node v24.22.2; pnpm 11.5.0 backend: cargo build --release --locked (packages/backend), path prefixes remapped to /build

SecureAnyCloud builds the binary from the unmodified upstream tag with the current stable Rust toolchain, on a separate build host. The image never contains a compiler.

The only change is the dependency lock file: eleven semver-compatible updates that fix every advisory cargo audit reports at the tag (zstd, zstd-safe and zstd-sys through async-compression and compression-codecs, tokio and anyhow). The web app is upstream's own build from its unmodified lock file.

All product and company names are trademarks or registered trademarks of their respective holders. SecureAnyCloud is not affiliated with or endorsed by them.

Support

SecureAnyCloud supports this image. Contact us at sales@secureanycloud.com. See the identity, secrets and PKI listing guide for the other versions in this listing.

Deploy on AWS

Launch Cryptgeon 2.9.3 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

Deploy on AWS