PrivateBin on Ubuntu 24.04 on Azure 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 URL fragment 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 disk so your encrypted pastes are captured into the image and re provisioned on every VM. 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 Azure data disk 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 (PrivateBin has no credentials to rotate)
- A static
/healthzendpoint for Azure Load Balancer health probes, with the config directory blocked from the web - 24/7 cloudimg support
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a good starting point. NSG inbound: allow 22/tcp from your management network and 80/tcp 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 localhost and HTTPS both provide).
Step 1 - Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for PrivateBin by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22) and HTTP (80). Then Review + create and Create.
Step 2 - Deploy from the Azure CLI
az vm create \
--resource-group <your-rg> \
--name privatebin \
--image <marketplace-image-urn> \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_ed25519.pub \
--vnet-name <your-vnet> --subnet <your-subnet> \
--public-ip-sku Standard
az vm open-port --resource-group <your-rg> --name privatebin --port 80 --priority 1010
Step 3 - Connect to your VM
ssh azureuser@<vm-public-ip>
Step 4 - 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 VM info note. Confirm they are healthy:
systemctl is-active nginx.service php8.3-fpm.service privatebin-firstboot.service
All three report active.

Step 5 - 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 6 - The encrypted paste store on a dedicated data disk
Every paste is stored as ciphertext under /var/lib/privatebin-data, which is a dedicated Azure data disk mounted separately from the OS disk and outside the web root. A non secret info note records the app URL for this VM:
findmnt -no SOURCE,TARGET,FSTYPE /var/lib/privatebin-data
grep -m1 'dir =' /var/www/privatebin/cfg/conf.php
cat /root/privatebin-credentials.txt
The data disk 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 7 - Open PrivateBin and start a new paste
Browse to http://<vm-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 8 - 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 9 - 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 10 - Read a paste back
Opening the full link in any browser fetches the ciphertext and decrypts it locally using the key from the URL fragment. The recipient sees the original content along with an expiry notice, and can Clone, download the raw text, or discuss it if you enabled comments.

Step 11 - 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 disk. Back up that directory (for example with an Azure disk 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 NSG. 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 Microsoft Azure. cloudimg is not affiliated with, endorsed by, or sponsored by the PrivateBin project.