How to Self-Host PrivateBin: Encrypted Pastebin Alternative 2026
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
| Feature | PrivateBin | Hastebin | Pastebin.com |
|---|---|---|---|
| License | ZLIB | MIT | Proprietary |
| Encryption | ✅ AES-256 client-side | ❌ | ❌ |
| Zero-knowledge | ✅ | ❌ | ❌ |
| Server sees content | ❌ Never | ✅ | ✅ |
| Burn after read | ✅ | ❌ | ❌ |
| Expiry options | ✅ | ❌ | ✅ (paid) |
| Syntax highlight | ✅ | ✅ | ✅ |
| Anonymous | ✅ | ✅ | Limited |
| 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
- Visit
https://paste.yourdomain.com - Paste your content
- Options:
- Expiration: 1 hour, 1 day, 1 week, etc.
- Burn after reading: ✓
- Password: (optional extra layer)
- Format: Plain text, code (syntax), Markdown
- Click Send
- 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.