PrivateBin Zero-Knowledge Pastebin on AWS User Guide
Overview
PrivateBin is a minimalist, open source pastebin where the server has zero knowledge of the data it stores. Every paste is encrypted and decrypted entirely in your browser using 256-bit AES-GCM; the decryption key lives only in the fragment of the share link and is never sent to the server, so the server only ever holds ciphertext. The cloudimg image serves PrivateBin over nginx and PHP-FPM on port 80, with the paste store on a dedicated data volume so your encrypted pastes are captured into the AMI and re-provisioned on every instance. There is no database and, by design, no login or admin account. Built from PrivateBin 2.0.5, backed by 24/7 cloudimg support.
What is included:
- PrivateBin 2.0.5 served from
/var/www/privatebinby nginx on:80in front of PHP 8.3 FPM - The Filesystem storage model pointed at a dedicated EBS data volume mounted at
/var/lib/privatebin-data nginx.service+php8.3-fpm.serviceas systemd units, enabled and active on every boot- A first-boot service that writes an MOTD banner and a non-secret info note recording the access URL for this instance (PrivateBin has no credentials to rotate)
- A static
/healthzendpoint for load-balancer health checks, with the config directory blocked from the web - 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 and 80/tcp (and 443/tcp if you terminate TLS) for the web interface. PrivateBin serves plain HTTP on port 80; for production, terminate TLS in front of it with your own domain so the site runs over HTTPS (the browser Web Crypto that PrivateBin relies on requires a secure context, which both localhost and HTTPS provide).
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 80 (and 443 for TLS).
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> \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=privatebin}]'
Step 2 - Connect to your instance
Connect over SSH as the default login user for the OS variant you launched. This listing may ship more than one OS variant; use the matching login user below.
| OS variant | SSH login user | Connect |
|---|---|---|
| Ubuntu 24.04 LTS | ubuntu |
ssh ubuntu@<instance-public-ip> |
Step 3 - Confirm the services are running
PrivateBin is served by nginx in front of PHP-FPM, with a one-shot first-boot service that writes the per-instance info note. Confirm they are healthy:
systemctl is-active nginx.service php8.3-fpm.service privatebin-firstboot.service
All three report active.
Step 4 - Verify the stack and endpoints
Confirm the PrivateBin version, the PHP runtime, the health endpoint, the app itself, and that the configuration file is never web-reachable:
grep -m1 'const VERSION' /var/www/privatebin/lib/Controller.php
php -r 'echo "PHP " . PHP_VERSION . "\n";'
curl -s -o /dev/null -w 'healthz: HTTP %{http_code}\n' http://127.0.0.1/healthz
curl -s -o /dev/null -w 'PrivateBin app: HTTP %{http_code}\n' http://127.0.0.1/
curl -s -o /dev/null -w 'cfg/conf.php: HTTP %{http_code} (denied)\n' http://127.0.0.1/cfg/conf.php
The app and /healthz return 200, while cfg/conf.php returns 403 because nginx blocks the config directory.
Step 5 - The encrypted paste store on a dedicated data volume
Every paste is stored as ciphertext under /var/lib/privatebin-data, a dedicated EBS data volume mounted separately from the OS disk and outside the web root. A non-secret info note records the app URL for this instance:
findmnt -no SOURCE,TARGET,FSTYPE /var/lib/privatebin-data
sudo grep -m1 'dir =' /var/www/privatebin/cfg/conf.php
sudo cat /root/privatebin-credentials.txt
The data volume is mounted at /var/lib/privatebin-data and the PrivateBin config points its storage dir at it. The info note is not a credential; PrivateBin has no login. It simply records the URL to open in a browser.
Step 6 - Open PrivateBin and start a new paste
Browse to http://<instance-public-ip>/ in any modern browser (or use your own HTTPS domain in production). The New Paste editor loads with the toolbar across the top: an expiry selector, Burn after reading, Open discussion, an optional password, and a format selector. There is no sign-in because the server never holds a decryption key.

Step 7 - Compose a paste
Type or paste your content into the editor. Choose how long it should live from Expires (from 5 minutes to never), pick a Format (Plain Text, Source Code with syntax highlighting, or Markdown), and optionally tick Burn after reading so the paste is destroyed the moment it is first opened, or set a Password for a second decryption factor.

Step 8 - Create and share the encrypted link
Click Create. PrivateBin encrypts the content in your browser, sends only the ciphertext to the server, and shows you a shareable link. The part of the link after the # is the decryption key: it stays in your browser and is never transmitted, so anyone with the full link can read the paste but the server never can. Use Copy link, Email, or QR code to share it.

Step 9 - Share by QR code
The QR code action renders the full share link, decryption key included, as a QR code so a recipient can open the encrypted paste on a phone without retyping the link.

Step 10 - Create a paste from the command line
PrivateBin also has a JSON API, so you can create pastes from scripts. The following creates a paste and reads it back, proving the round trip end to end (the payload is opaque ciphertext, exactly what a browser would send):
IV="$(openssl rand -base64 16 | tr -d '\n')"; SALT="$(openssl rand -base64 8 | tr -d '\n')"; CT="$(openssl rand -base64 48 | tr -d '\n')"
REQ="$(printf '{"v":2,"adata":[["%s","%s",100000,256,128,"aes","gcm","none"],"plaintext",0,0],"ct":"%s","meta":{"expire":"5min"}}' "$IV" "$SALT" "$CT")"
CREATE="$(curl -s -H 'X-Requested-With: JSONHttpRequest' -H 'Content-Type: application/json' --data-binary "$REQ" http://127.0.0.1/)"
PID="$(printf '%s' "$CREATE" | sed -n 's/.*"id":"\([0-9a-f]*\)".*/\1/p')"
READ="$(curl -s -H 'X-Requested-With: JSONHttpRequest' "http://127.0.0.1/?$PID")"
case "$CREATE$READ" in *'"status":0'*'"ct":'*) echo "OK - created and read paste $PID" ;; *) echo "FAILED"; exit 1 ;; esac
It prints OK - created and read paste <id>. Note the server stores only the ciphertext you send; a real client encrypts the content and keeps the key in the URL fragment.
Maintenance
- Data: encrypted pastes live under
/var/lib/privatebin-dataon the dedicated data volume. Back up that directory (for example with an EBS snapshot) to preserve pastes. Expired and burn-after-reading pastes are purged automatically. - Traffic limiter: this appliance ships with PrivateBin's IP-based traffic limiter disabled (
limit = 0in/var/www/privatebin/cfg/conf.php) because it is single-tenant behind your security group. To rate-limit posting again, setlimit = 10and reload nginx. - File upload: file attachments are off by default. To enable them, set
fileupload = trueunder[main]in/var/www/privatebin/cfg/conf.php. - TLS: PrivateBin serves plain HTTP on port 80. Front it with TLS (for example certbot) and your own domain before production use, so the browser runs in a secure context over HTTPS.
- Restart:
sudo systemctl restart nginx.service php8.3-fpm.serviceif you need to bounce the web stack. - Security patches: unattended-upgrades remains enabled so the OS continues to receive security updates automatically.
Support
cloudimg provides 24/7 expert support for this image. Contact support@cloudimg.co.uk.
This image packages the open source PrivateBin software (Zlib/libpng License) for convenient deployment on AWS. cloudimg is not affiliated with, endorsed by, or sponsored by the PrivateBin project.