PrivateBin: Self-Hosted Encrypted Pastebin
Zero-knowledge encrypted pastebin. Share text and files securely with expiring links.
Introduction
PrivateBin is a minimalist, open-source online pastebin where the server has zero knowledge of pasted data. All encryption and decryption happens in the browser using 256-bit AES, making it impossible for the server operator to read the content. Perfect for securely sharing passwords, code snippets, configuration files, or sensitive information with automatic expiration and burn-after-reading features.
By the end of this guide, you’ll have a production-ready PrivateBin installation that:
- Encrypts all data client-side before sending to the server
- Supports text pastes and file attachments up to 10MB
- Offers burn-after-reading (self-destruct after first view)
- Provides configurable expiration times
- Includes discussion threads for collaborative notes
- Features syntax highlighting for code
- Maintains complete privacy with zero-knowledge architecture
Prerequisites
Before starting, ensure you have:
- A Linux server (VM, VPS, or bare metal) with at least 256MB RAM
- Docker and Docker Compose installed
- A domain name (optional but recommended for HTTPS)
- Port 80 and 443 available (or alternative ports)
- Root or sudo access
- At least 5GB free disk space
- Basic understanding of Docker
Why Use PrivateBin?
Zero-Knowledge Architecture
Client-Side Encryption:
- All encryption happens in your browser before data leaves your device
- Server only stores encrypted blobs
- Even server administrators cannot read content
- Decryption key never sent to server
Privacy by Design:
- No logs or tracking
- No cookies required
- No JavaScript analytics
- Open source and auditable
Use Cases
Secure Sharing:
- Passwords and API keys
- Configuration files with secrets
- Private code snippets
- Personal notes and documents
- Incident reports
Collaboration:
- Bug reports with sensitive data
- Security vulnerability reports
- Team credentials sharing
- Temporary file exchange
Ephemeral Data:
- One-time secret sharing
- Time-limited access to information
- Self-destructing messages
Architecture Overview
Our deployment consists of:
- PrivateBin Container: Core pastebin application
- Web Server: Nginx for reverse proxy and HTTPS
- Storage: File-based or Redis for paste storage
- Optional: Let’s Encrypt for automatic SSL
Data flow:
- User creates paste in browser
- Data encrypted with AES-256 in JavaScript
- Encrypted blob sent to server with metadata
- Server stores blob without decryption key
- Share link includes decryption key in URL fragment
- Recipient’s browser decrypts content
Directory Structure Setup
Create the necessary directory structure:
mkdir -p ~/privatebin/{data,nginx,scripts,backups}
cd ~/privatebin Create subdirectories:
mkdir -p data/uploads
mkdir -p nginx/conf.d Docker Compose Configuration
Create a docker-compose.yml file:
version: '3.8'
services:
privatebin:
image: privatebin/nginx-fpm-alpine:latest
container_name: privatebin
restart: unless-stopped
read_only: true
volumes:
- ./data:/srv/data
- ./conf.php:/srv/cfg/conf.php:ro
ports:
- "8080:8080"
environment:
TZ: America/New_York
PHP_TZ: America/New_York
tmpfs:
- /tmp:rw,noexec,nosuid,size=64M
- /var/tmp:rw,noexec,nosuid,size=64M
networks:
- privatebin_net
nginx:
image: nginx:alpine
container_name: privatebin_nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
networks:
- privatebin_net
depends_on:
- privatebin
networks:
privatebin_net:
driver: bridge PrivateBin Configuration
Create a conf.php file for PrivateBin configuration:
<?php
// Configuration file for PrivateBin
[main]
; Name displayed in the title bar
name = "PrivateBin"
; Discussion forum feature
discussion = true
; Allow users to set password protection
password = true
; Allow file uploads
fileupload = true
; Burn after reading feature
burnafterreadingselected = false
; Default expiration time (5min, 10min, 1hour, 1day, 1week, 1month, 1year, never)
default = "1week"
; Show clone button
clone = true
; QR code generation
qrcode = true
; Email notification when paste is read
email = true
; Show visitor counter
zerobincompatibility = false
; Template to use (bootstrap, page)
template = "bootstrap"
; Syntax highlighting
syntaxhighlightingtheme = "sons-of-obsidian"
; Size limit for attachments (10 MiB)
sizelimit = 10485760
; Compression type (zlib, none)
compression = "zlib"
[expire]
; Expiration options available
default = "1week"
[expire_options]
; Seconds, minutes, hours, days, weeks, months, years
5min = 300
10min = 600
1hour = 3600
1day = 86400
1week = 604800
1month = 2592000
1year = 31536000
never = 0
[traffic]
; Time limit between calls from the same IP
limit = 10
; Traffic directory
dir = PATH "data"
[purge]
; Minimum time between purge runs
limit = 300
; Path to purge script
dir = PATH "data"
[model]
; Storage backend (Filesystem, Database, S3, GoogleCloudStorage)
class = Filesystem
[model_options]
; Directory for file-based storage
dir = PATH "data" Nginx Reverse Proxy Configuration
Create nginx/conf.d/privatebin.conf:
server {
listen 80;
server_name paste.yourdomain.com;
# Redirect to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name paste.yourdomain.com;
# SSL Configuration
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer" always;
add_header Content-Security-Policy "default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none';" always;
# Max upload size
client_max_body_size 10M;
location / {
proxy_pass http://privatebin:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Logging
access_log /var/log/nginx/privatebin_access.log;
error_log /var/log/nginx/privatebin_error.log;
} SSL Certificate Setup
Option 1: Self-Signed Certificate (Testing)
Generate a self-signed certificate:
mkdir -p nginx/ssl
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout nginx/ssl/key.pem -out nginx/ssl/cert.pem -subj "/C=US/ST=State/L=City/O=Organization/CN=paste.yourdomain.com" Option 2: Let’s Encrypt (Production)
Install certbot:
sudo apt update
sudo apt install certbot Obtain certificate:
sudo certbot certonly --standalone -d paste.yourdomain.com Copy certificates:
sudo cp /etc/letsencrypt/live/paste.yourdomain.com/fullchain.pem nginx/ssl/cert.pem
sudo cp /etc/letsencrypt/live/paste.yourdomain.com/privkey.pem nginx/ssl/key.pem
sudo chown $USER:$USER nginx/ssl/*.pem Set up auto-renewal:
sudo crontab -e Add:
0 3 * * * certbot renew --quiet && cp /etc/letsencrypt/live/paste.yourdomain.com/fullchain.pem /path/to/privatebin/nginx/ssl/cert.pem && cp /etc/letsencrypt/live/paste.yourdomain.com/privkey.pem /path/to/privatebin/nginx/ssl/key.pem && docker-compose restart nginx Starting PrivateBin
Start all services:
docker-compose up -d Verify containers are running:
docker-compose ps Check logs:
docker-compose logs -f privatebin
docker-compose logs -f nginx Access your PrivateBin instance:
https://paste.yourdomain.com Using PrivateBin
Creating a Paste
Basic Text Paste:
- Navigate to your PrivateBin URL
- Enter or paste text in the main editor
- Select expiration time (5 min to never)
- Optional: Enable “Burn after reading”
- Optional: Add password protection
- Optional: Enable “Open discussion”
- Click “Send”
- Copy the generated URL
The URL Structure:
https://paste.yourdomain.com/?a1b2c3d4e5f6g7h8#i9j0k1l2m3n4o5p6
|_______________| |_______________|
Paste ID (server) Encryption key (client) The key after the hash (#) never reaches the server.
Attaching Files
Upload a File:
- Click “Attach a file” tab
- Select file (max 10MB by default)
- File is encrypted client-side
- Set expiration and options
- Click “Send”
- Share URL with recipient
File Types:
- Documents (PDF, DOCX, TXT)
- Images (PNG, JPG, GIF)
- Archives (ZIP, TAR, GZ)
- Code files
- Configuration files
Password Protection
Add Password to Paste:
- Create your paste
- Check “Password (recommended)”
- Enter password
- Click “Send”
- Share URL and password separately via different channels
Best Practice:
- Use strong, unique passwords
- Share URL and password via different communication methods
- Consider using password + burn after reading for maximum security
Burn After Reading
Self-Destructing Pastes:
- Create paste
- Enable “Burn after reading”
- Click “Send”
- Paste will be permanently deleted after first view
- Viewer gets one-time access only
Use Cases:
- One-time password sharing
- Temporary API keys
- Sensitive documents
- Emergency access codes
Discussion Feature
Open Discussion:
- Create paste
- Enable “Open discussion”
- Click “Send”
- Recipients can reply with comments
- All comments are encrypted
- Discussion thread preserved until expiration
Collaboration:
- Bug reports with Q&A
- Shared troubleshooting
- Temporary project notes
- Team coordination
Syntax Highlighting
PrivateBin automatically detects and highlights code:
Supported Languages:
- Python, JavaScript, PHP, Ruby, Go
- Java, C, C++, C#, Rust
- HTML, CSS, SQL, Bash
- JSON, YAML, XML, Markdown
- And 150+ more languages
Manual Selection:
- Paste code
- Click “Format” button
- Select language from dropdown
- Syntax highlighting applied automatically
Advanced Configuration
Redis Backend for Better Performance
For high-traffic instances, use Redis instead of filesystem:
Add to docker-compose.yml:
redis:
image: redis:alpine
container_name: privatebin_redis
restart: unless-stopped
volumes:
- ./redis:/data
networks:
- privatebin_net Update conf.php:
[model]
class = Database
[model_options]
dsn = "mysql:host=mysql;dbname=privatebin;charset=UTF8"
tbl = "privatebin_"
usr = "privatebin"
pwd = "SECURE_PASSWORD"
opt[12] = true Or for Redis:
[model]
class = Filesystem
[model_options]
dir = PATH "data" S3 Storage Backend
For distributed deployments:
[model]
class = S3
[model_options]
region = "us-east-1"
version = "latest"
endpoint = "https://s3.amazonaws.com"
bucket = "my-privatebin-bucket"
prefix = "pastes/" Set AWS credentials in docker-compose.yml:
environment:
AWS_ACCESS_KEY_ID: "your-key-id"
AWS_SECRET_ACCESS_KEY: "your-secret-key" Custom Expiration Times
Modify conf.php to add custom expiration options:
[expire_options]
30min = 1800
6hours = 21600
3days = 259200
2weeks = 1209600
3months = 7776000 Rate Limiting
Prevent abuse with traffic limiting:
[traffic]
; Minimum seconds between requests from same IP
limit = 10
; Enable traffic limiter
exempted = "127.0.0.1,192.168.1.0/24" Size Limits
Adjust maximum paste size:
[main]
; 50 MiB limit
sizelimit = 52428800
; Or disable limits entirely (not recommended)
sizelimit = 0 Update nginx configuration to match:
client_max_body_size 50M; Security Best Practices
Enable HTTPS Only
Always use HTTPS in production:
- Obtain valid SSL certificate (Let’s Encrypt recommended)
- Configure strong SSL/TLS settings
- Enable HTTP Strict Transport Security (HSTS)
- Force HTTPS redirects
Content Security Policy
The provided nginx config includes strict CSP headers:
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; This prevents:
- XSS attacks
- Data injection
- Clickjacking
- Unauthorized resource loading
Firewall Configuration
Restrict access to PrivateBin:
# Allow HTTPS
sudo ufw allow 443/tcp
# Allow HTTP (for Let's Encrypt validation)
sudo ufw allow 80/tcp
# Block direct access to container port
sudo ufw deny 8080/tcp
# Enable firewall
sudo ufw enable Regular Updates
Keep PrivateBin updated:
# Pull latest image
docker-compose pull
# Recreate containers
docker-compose up -d Disable Unnecessary Features
If you don’t need certain features, disable them in conf.php:
[main]
; Disable file uploads
fileupload = false
; Disable discussions
discussion = false
; Disable email notifications
email = false
; Disable QR codes
qrcode = false Backup Procedures
Automated Backup Script
Create scripts/backup-privatebin.sh:
#!/bin/bash
BACKUP_DIR="./backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="privatebin_backup_$TIMESTAMP"
# Create backup directory
mkdir -p "$BACKUP_DIR"
echo "Starting PrivateBin backup..."
# Backup data directory
echo "Backing up pastes and data..."
tar -czf "$BACKUP_DIR/$BACKUP_NAME.tar.gz" data/ conf.php
# Backup nginx configuration
tar -czf "$BACKUP_DIR/nginx_config_$TIMESTAMP.tar.gz" nginx/
# Keep only last 30 backups
echo "Cleaning old backups (keeping last 30)..."
cd "$BACKUP_DIR"
ls -t privatebin_backup_*.tar.gz | tail -n +31 | xargs -r rm
echo "Backup completed: $BACKUP_DIR/$BACKUP_NAME.tar.gz" Make executable:
chmod +x scripts/backup-privatebin.sh Automated Backups with Cron
crontab -e Add daily backup at 3 AM:
0 3 * * * cd /path/to/privatebin && ./scripts/backup-privatebin.sh >> ./backups/backup.log 2>&1 Restore Procedure
Create scripts/restore-privatebin.sh:
#!/bin/bash
if [ -z "$1" ]; then
echo "Usage: $0 backup_file.tar.gz"
exit 1
fi
BACKUP_FILE="$1"
echo "Restoring from $BACKUP_FILE..."
# Stop containers
docker-compose down
# Backup current state
mv data data.bak_$(date +%Y%m%d_%H%M%S)
# Extract backup
tar -xzf "$BACKUP_FILE"
# Start containers
docker-compose up -d
echo "Restore completed successfully!" Make executable:
chmod +x scripts/restore-privatebin.sh Monitoring and Maintenance
Health Check Script
Create scripts/healthcheck.sh:
#!/bin/bash
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'
echo "=== PrivateBin Health Check ==="
echo ""
# Check containers
echo -n "PrivateBin container: "
if docker ps | grep -q privatebin; then
echo -e "${GREEN}Running${NC}"
else
echo -e "${RED}Not running${NC}"
fi
echo -n "Nginx container: "
if docker ps | grep -q privatebin_nginx; then
echo -e "${GREEN}Running${NC}"
else
echo -e "${RED}Not running${NC}"
fi
# Test HTTPS
echo -n "HTTPS connectivity: "
if curl -sk https://paste.yourdomain.com | grep -q "PrivateBin"; then
echo -e "${GREEN}Working${NC}"
else
echo -e "${RED}Failed${NC}"
fi
# Check disk space
echo ""
echo "Disk usage:"
df -h | grep -E "Filesystem|/mnt|/$"
# Check paste storage
echo ""
echo "Paste storage:"
du -sh data/
echo ""
echo "Number of pastes:"
find data/ -name "*.php" 2>/dev/null | wc -l Make executable:
chmod +x scripts/healthcheck.sh Purge Expired Pastes
PrivateBin automatically purges expired pastes, but you can manually trigger:
docker exec privatebin php /srv/bin/purge Schedule regular purge:
crontab -e Add:
0 */6 * * * docker exec privatebin php /srv/bin/purge >> /path/to/privatebin/logs/purge.log 2>&1 Log Management
Monitor logs for issues:
# View recent logs
docker-compose logs --tail=50 privatebin
# Follow logs in real-time
docker-compose logs -f privatebin
# Search for errors
docker-compose logs privatebin | grep -i error Rotate logs:
# Add to cron
0 0 * * 0 docker-compose logs --no-color > /path/to/privatebin/logs/privatebin_$(date +%Y%m%d).log 2>&1 Mobile Device Configuration
iOS (iPhone/iPad)
Using PrivateBin:
- Open Safari or your preferred browser
- Navigate to your PrivateBin URL
- Bookmark for easy access
- Add to Home Screen:
- Share Add to Home Screen
- Icon appears like native app
Sharing from iOS:
- Copy text you want to share
- Open PrivateBin in browser
- Paste content
- Set expiration and options
- Send and share URL via Messages, Email, etc.
Android
Using PrivateBin:
- Open Chrome or your preferred browser
- Navigate to your PrivateBin URL
- Bookmark for easy access
- Add to Home Screen:
- Menu Add to Home screen
- Icon appears like native app
Browser Extensions:
- Install “PrivateBin Share” (if available)
- Enables sharing directly from Share menu
Performance Optimization
Nginx Caching
Add to nginx configuration:
# Cache static assets
location ~* .(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Gzip compression
gzip on;
gzip_vary on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
gzip_min_length 1000; PHP-FPM Tuning
For high-traffic sites, optimize PHP-FPM.
Create php-fpm.conf:
[www]
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
pm.max_requests = 500 Mount in docker-compose.yml:
volumes:
- ./php-fpm.conf:/etc/php8/php-fpm.d/www.conf:ro Resource Monitoring
Monitor container resources:
# Real-time stats
docker stats privatebin
# Memory usage
docker exec privatebin free -h
# Disk usage
docker exec privatebin df -h Troubleshooting
Common Issues
Issue: Cannot access PrivateBin
# Check container status
docker-compose ps
# Check logs
docker-compose logs privatebin
docker-compose logs nginx
# Verify nginx configuration
docker exec privatebin_nginx nginx -t
# Test direct container access
curl http://localhost:8080 Issue: SSL certificate errors
# Verify certificate files exist
ls -la nginx/ssl/
# Check certificate validity
openssl x509 -in nginx/ssl/cert.pem -text -noout
# Verify nginx can read certificates
docker exec privatebin_nginx ls -la /etc/nginx/ssl/ Issue: File uploads failing
- Check
sizelimitinconf.php - Verify
client_max_body_sizein nginx config - Check disk space:
df -h - Review logs:
docker-compose logs privatebin | grep -i upload
Issue: Pastes not expiring
- Verify purge is running:
docker exec privatebin php /srv/bin/purge - Check purge configuration in
conf.php - Set up cron job for automatic purging
Debugging
Enable debug mode in conf.php:
[main]
; Enable error display (disable in production)
debug = true View detailed logs:
docker-compose logs -f privatebin Integration and Automation
Browser Bookmarklet
Create a bookmarklet for quick paste creation:
javascript:(function(){var%20url='https://paste.yourdomain.com/?';var%20text=window.getSelection().toString()||prompt('Enter text to paste:');if(text){window.open(url+'text='+encodeURIComponent(text))}})(); Save as bookmark, click to paste selected text.
Command-Line Integration
Create a shell function for CLI usage:
# Add to ~/.bashrc or ~/.zshrc
paste() {
if [ -z "$1" ]; then
echo "Usage: paste filename-or-text"
return 1
fi
if [ -f "$1" ]; then
curl -X POST https://paste.yourdomain.com/api/ -F "file=@$1" -F "expire=1week"
else
echo "$1" | curl -X POST https://paste.yourdomain.com/api/ -F "text=<-" -F "expire=1week"
fi
} Usage:
# Paste file
paste myfile.txt
# Paste text
paste "This is my secret text"
# Paste command output
cat config.yaml | paste - API Usage
PrivateBin has a simple API:
Create paste via API:
# Text paste
curl -X POST https://paste.yourdomain.com/api/ -H "Content-Type: application/json" -d '{
"paste": "My secret text",
"expire": "1day",
"burnafterreading": 0
}'
# File upload
curl -X POST https://paste.yourdomain.com/api/ -F "[email protected]" -F "expire=1week" Response:
{
"status": 0,
"id": "abc123",
"url": "https://paste.yourdomain.com/?abc123",
"deletetoken": "def456"
} Conclusion
You now have a fully functional self-hosted PrivateBin installation with:
- Client-side end-to-end encryption for maximum privacy
- Zero-knowledge architecture where server cannot read content
- Support for text pastes and file attachments
- Burn-after-reading for self-destructing messages
- Configurable expiration times
- Password protection for extra security
- Discussion threads for collaboration
- Syntax highlighting for code
- HTTPS with automatic certificate management
PrivateBin provides a secure, private alternative to public pastebin services while maintaining complete control over your data.
Next Steps
Consider these enhancements:
- Set up high availability with load balancing
- Implement Redis cluster for distributed storage
- Configure S3 backend for unlimited storage
- Add custom branding and themes
- Implement rate limiting with fail2ban
- Set up monitoring with Prometheus and Grafana
- Create custom API integrations
- Implement backup automation to cloud storage
For support and updates, visit:
- PrivateBin GitHub: https://github.com/PrivateBin/PrivateBin
- PrivateBin Documentation: https://github.com/PrivateBin/PrivateBin/wiki
- PrivateBin Forums: https://github.com/PrivateBin/PrivateBin/discussions