Skip to main content

How to Self-Host PrivateBin: Encrypted Pastebin Alternative 2026

·OSSAlt Team
privatebinpastebinprivacyencryptionself-hostingdocker2026

TL;DR

PrivateBin is a zero-knowledge pastebin — ZLIB license (permissive), ~6K GitHub stars, PHP. All encryption/decryption happens in the browser with AES-256-GCM — the server never sees your paste content. Share code snippets, passwords, and sensitive text with end-to-end encryption. Deploy in 2 minutes with Docker. Also covers Hastebin (MIT, Node.js) for teams that want simpler non-encrypted paste storage.

Key Takeaways

  • PrivateBin: ZLIB license, ~6K stars, PHP — zero-knowledge encryption
  • Zero-knowledge: Encryption key in URL fragment (#), never sent to server
  • Burn after reading: Paste destroyed after first view
  • Expiry: 5 minutes to 1 year, or never
  • No account: Anonymous by default, no registration
  • Password protection: Optional password on top of encryption

PrivateBin vs Hastebin vs Pastebin.com

FeaturePrivateBinHastebinPastebin.com
LicenseZLIBMITProprietary
Encryption✅ AES-256 client-side
Zero-knowledge
Server sees content❌ Never
Burn after read
Expiry options✅ (paid)
Syntax highlight
AnonymousLimited
Password protect
API

When to use PrivateBin: Sharing secrets, passwords, API keys, sensitive code. When to use Hastebin: Simple code sharing within a trusted team without encryption overhead.


Part 1: PrivateBin Docker Setup

# docker-compose.yml
version: '3.8'

services:
  privatebin:
    image: privatebin/nginx-fpm-alpine:latest
    container_name: privatebin
    restart: unless-stopped
    read_only: true
    ports:
      - "8080:8080"
    volumes:
      - privatebin_data:/srv/data    # Encrypted paste storage
      - ./conf.php:/srv/cfg/conf.php:ro  # Custom configuration

volumes:
  privatebin_data:

Configuration

<?php
# conf.php

[main]
; Enable burn after reading option:
burnafterreading = true

; Default discussion mode (true/false):
discussion = false

; Require password (all pastes must be password-protected):
password = true

; Default expiration:
; 5min, 10min, 1hour, 1day, 1week, 1month, 1year, never
defaultexpiration = 1week

; Syntax highlighting language:
defaultformatter = plaintext

; File uploads (optional):
fileupload = false

; Paste limit per IP:
sizelimit = 10485760    ; 10MB

[traffic]
; Minimum time between two pastes (seconds):
limit = 10

; IP based flood protection:
header =

[model]
class = Filesystem

[model_options]
dir = PATH "data"
docker compose up -d

Visit http://your-server:8080.


Part 2: HTTPS with Caddy

paste.yourdomain.com {
    reverse_proxy localhost:8080
}

Part 3: How PrivateBin Zero-Knowledge Works

Understanding the encryption model:

1. You write paste content in browser
2. Browser generates random 256-bit key (never sent to server)
3. Browser encrypts content with AES-256-GCM using that key
4. Encrypted ciphertext sent to server
5. Server stores only ciphertext (can't read it)
6. Share URL: https://paste.yourdomain.com/?id=abc123#base64encodedkey
                                                         ^
                                         Key is in fragment (#)
                                         Fragment never sent to server

7. Recipient opens URL → browser extracts key from #fragment
8. Browser fetches ciphertext, decrypts locally
9. Server only sees: "give me paste abc123" — never the key

Result: Even if your server is compromised, all pastes remain unreadable without the URL fragment.


Part 4: Using PrivateBin

Web Interface

  1. Visit https://paste.yourdomain.com
  2. Paste your content
  3. Options:
    • Expiration: 1 hour, 1 day, 1 week, etc.
    • Burn after reading: ✓
    • Password: (optional extra layer)
    • Format: Plain text, code (syntax), Markdown
  4. Click Send
  5. Share the URL

CLI (curl)

# Paste content via CLI:
echo '{"v":2,"ct":"your-encrypted-content"}' | \
  curl -s -X POST https://paste.yourdomain.com/ \
  -H "Content-Type: application/json" \
  --data-binary @-

# Or use pb (PrivateBin CLI tool):
pip install privatebin
# Configure:
pb set-url https://paste.yourdomain.com

# Create a paste:
cat secret.txt | pb
# Returns paste URL

# Burn after read:
cat credentials.txt | pb --burn

# Expiry:
cat code.py | pb --expire 1hour

Share Secrets Between Team Members

# Share a database password securely:
echo "POSTGRES_PASSWORD=SuperSecret123!" | pb --burn --expire 1hour

# Share an SSH key:
pb < ~/.ssh/id_ed25519

# Share environment variables:
cat .env | pb --expire 1week

Option 2: Hastebin — Simple Team Pastebin

Hastebin is a simple, fast pastebin for teams — no encryption, just quick code sharing with syntax highlighting and an intuitive API.

Docker Setup

services:
  hastebin:
    image: ghcr.io/nicholaswilde/haste-server:latest
    container_name: hastebin
    restart: unless-stopped
    ports:
      - "7777:7777"
    environment:
      STORAGE_TYPE: file   # or: redis, postgres, mongo
      STORAGE_PATH: /app/data
    volumes:
      - hastebin_data:/app/data

volumes:
  hastebin_data:
paste.yourdomain.com {
    reverse_proxy localhost:7777
}

Hastebin Usage

# Web: paste and get a URL like paste.yourdomain.com/abcde
# CLI:
echo "hello world" | curl -X POST -s -d @- https://paste.yourdomain.com/documents | \
  python3 -c "import sys,json;print('https://paste.yourdomain.com/'+json.load(sys.stdin)['key'])"

# Or install haste CLI:
npm install -g haste-client
# Set endpoint:
export HASTE_SERVER=https://paste.yourdomain.com
# Use:
cat code.js | haste

Part 5: Integrate with Shell

Add paste function to .bashrc/.zshrc

# PrivateBin paste function:
pb() {
  local content
  if [ -p /dev/stdin ]; then
    content=$(cat)
  else
    content="$*"
  fi

  curl -s -X POST https://paste.yourdomain.com/ \
    -H "Content-Type: application/json" \
    -d "{\"v\":2,\"adata\":[[\"pbkdf2\",\"sha256\",100000,256],1,1],\"ct\":\"$(echo "$content" | base64)\"}" \
  | jq -r '"https://paste.yourdomain.com/?" + .id + "#" + .key'
}

# Usage:
git diff | pb
cat /etc/hosts | pb
pb "some quick note"

Git Integration (diff sharing)

# Alias in .gitconfig:
[alias]
  share-diff = "!git diff | pb"
  share-log = "!git log --oneline -20 | pb"

Part 6: Admin and Cleanup

PrivateBin automatically handles expiry based on configured paste expiration. No manual cleanup needed.

For manual cleanup of expired pastes:

# Via Docker exec:
docker exec privatebin /bin/sh -c "php -r \"
\\\$model = new PrivateBin\Data\Filesystem(['dir' => 'PATH \"data\"']);
\\\$model->purge(0);
echo 'Purged expired pastes\n';
\""

Configure automatic purge in conf.php:

[purge]
; 0 = no purge, otherwise time in seconds between purge runs
batchsize = 10

Maintenance

# Update PrivateBin:
docker compose pull
docker compose up -d

# Backup pastes (encrypted blobs):
tar -czf privatebin-data-$(date +%Y%m%d).tar.gz \
  $(docker volume inspect privatebin_privatebin_data --format '{{.Mountpoint}}')

See all open source privacy tools at OSSAlt.com/categories/privacy.

Comments