Exploring how to self-host an end-to-end encrypted pastebin using Paaster and Cloudflare R2 storage. I'd like to easily share sensitive information like code snippets or logs with others without the need to trust a third-party service with capabilities like access code protection, expiration, view limits, etc.
Paaster
Paaster is a secure and user-friendly pastebin application that allows you to store and share text snippets. It features end-to-end encryption so that only people you share the link with can view the paste. It also supports access code protection, expiration, rate limiting, share via QR code, and more. There is support for multiple storage backends, including local file storage, AWS S3, Cloudflare R2, Google Cloud Storage, MinIO, Storj, and more. Easily deployable with Docker and even support for Vercel.
Cloudflare R2
Cloudflare R2 is a serverless edge storage platform that provides a simple, secure, and scalable way to store and serve data, similar to AWS S3 and other providers. Though this isn't quite "Self-Hosted", I'm pretty happy with it since the data is encrypted prior to being stored in the bucket, and only I have access to the encryption keys.
Guide
I primarily followed the Paaster Setup Instructions and started from the docker-public-s3/docker-compose.yml example. Paaster's frontend can be served with Vercel, but I opted to self-host both the frontend and backend, but with Cloudflare R2 as the storage backend.
DNS Configuration.
Choose a subdomain for your Paaster instance, e.g.,
paaster.example.com. Update theCNAMErecords to point to where you will host the frontend / backend. For example:paaster.example.com- frontendapi-paaster.example.com- backend
Cloudflare R2 Configuration.
- Create a new R2 bucket in the Cloudflare dashboard.
paaster - Enable Custom Domains for your bucket. Pick something like
r2.paaster.example.com. This will need to be different from your frontend domain. - Configure Cross-Origin Resource Sharing (CORS) for your bucket. This is required for the frontend to interact with the R2 bucket.
Allowed Origins:https://paaster.example.com
Allowed Methods:GET - Retrieve the following from the R2 bucket settings, for example:
- Location:
Western North America (WNAM) - S3 API URL. This will be used in the backend configuration.
https://***.r2.cloudflarestorage.com/paaster
- Location:
- Create a new Access Key and Secret Key for the R2 bucket, see R2 Authentication. These will be used in the backend configuration.
paaster- Token Name:
paaster - Permissions:
Object Read & Write - Apply to specific buckets only:
paaster - Note the Access Key and Secret Key.
- Token Name:
- Create a new R2 bucket in the Cloudflare dashboard.
Backend & Frontend Configuration - Self-Hosted Docker-Compose.
Download the example docker-public-s3/docker-compose.yml file.
Update the backend:
ymlpaaster_backend: ... environment: ... paaster_s3: > '{ "endpoint_url": "<R2_S3_API_URL>", # https://***.r2.cloudflarestorage.com/paaster "secret_access_key": "<R2_SECRET_ACCESS_KEY>", "access_key_id": "<R2_ACCESS_KEY_ID>", "region_name": "WNAM", "bucket": "paaster", "folder": "pastes", "download_url": "https://r2.paaster.example.com" }' paaster_proxy_urls: > '{ "frontend": "https://paaster.example.com", "backend": "http://api-paaster.example.com" }'Update the frontend:
ymlpaaster_frontend: ... environment: ... VITE_NAME: "paaster.example.com" VITE_API_URL: "http://api-paaster.example.com"Start the backend with
docker-compose up -d.Verify logs are healthy with
docker-compose logs -f
Reverse Proxy Configuration.
Proxy exposed ports to the frontend and backend services using whatever reverse proxy you prefer, like Nginx, Traefik, Caddy, etc. For example, with
nginx:nginx# Frontend server { listen 443 ssl http2; server_name paaster.*; client_max_body_size 0; location / { include /config/nginx/proxy.conf; include /config/nginx/resolver.conf; set $upstream_app <paaster_frontend_ip>; set $upstream_port 8889; set $upstream_proto http; proxy_pass $upstream_proto://$upstream_app:$upstream_port; # Security Headers add_header Strict-Transport-Security "max-age=31536000" always; add_header X-XSS-Protection "1; mode=block" always; add_header X-Frame-Options "DENY" always; add_header Feature-Policy "microphone 'none'; camera 'none'; geolocation 'none'; payment 'none'" always; # Remove the proxy_hide_header directive for X-Frame-Options if setting it explicitly proxy_hide_header X-Frame-Options; } } # Backend server { listen 443 ssl http2; server_name api-paaster.*; client_max_body_size 0; location / { include /config/nginx/proxy.conf; include /config/nginx/resolver.conf; set $upstream_app <paaster_backend_ip>; set $upstream_port 8888; set $upstream_proto http; proxy_pass $upstream_proto://$upstream_app:$upstream_port; proxy_hide_header X-Frame-Options; } }Try it Out!.
- Visit your new
https://paaster.example.comand create a new paste. - Share the paste with others securely!
- Visit your new
Final Thoughts
Paaster is a fantastic self-hosted pastebin solution that provides end-to-end encrypted pastes that I can share with others securely. The setup was straightforward and I was able to get everything up and running. I'm excited to start using Paaster to share sensitive information with others securely!