Paperclip nginx Reverse Proxy Configuration
Running Paperclip behind nginx is the standard pattern for production VPS deployments. nginx handles SSL termination, WebSocket proxying, and HTTP/2 while Paperclip focuses on application logic. Here's a battle-tested configuration.
Deploy on Railway instead →Why nginx in front of Paperclip
Paperclip listens on 127.0.0.1:3100 by default — not accessible from the internet. nginx sits in front and:
- Handles HTTPS and certificate management
- Proxies requests to Paperclip
- Supports WebSocket connections (used by the live event system)
- Serves as a security layer (rate limiting, header hardening)
Basic configuration
Create /etc/nginx/sites-available/paperclip:
server {
listen 80;
server_name paperclip.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:3100;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_send_timeout 300;
}
}
Enable it and reload:
sudo ln -s /etc/nginx/sites-available/paperclip /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Then add SSL:
sudo certbot --nginx -d paperclip.yourdomain.com
Full production configuration (with SSL)
After running certbot, your config becomes:
server {
listen 80;
server_name paperclip.yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name paperclip.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/paperclip.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/paperclip.yourdomain.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;
add_header Referrer-Policy strict-origin-when-cross-origin;
# Proxy to Paperclip
location / {
proxy_pass http://127.0.0.1:3100;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_send_timeout 300;
proxy_buffering off;
}
}
WebSocket support
Paperclip uses WebSockets for its live events system (real-time updates in the UI). The key headers are:
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
Without these, the WebSocket handshake fails and the UI doesn't receive live updates. Always include them.
Handling large file uploads
If agents upload large files through Paperclip, increase nginx's client body size limit:
server {
client_max_body_size 100M; # Allow up to 100 MB uploads
...
}
The default is 1 MB, which will cause 413 errors for larger uploads.
Rate limiting (optional)
Protect your Paperclip instance from brute-force attempts:
# Add to /etc/nginx/nginx.conf, inside the http block
limit_req_zone $binary_remote_addr zone=paperclip_api:10m rate=30r/m;
# Then in your server block:
location /api/auth {
limit_req zone=paperclip_api burst=10 nodelay;
proxy_pass http://127.0.0.1:3100;
...
}
This limits authentication endpoints to 30 requests/minute per IP.
Serving static assets (optional optimization)
If you want nginx to cache static files instead of proxying them:
location ~* \.(js|css|png|jpg|gif|ico|svg|woff2)$ {
proxy_pass http://127.0.0.1:3100;
proxy_cache_bypass $http_upgrade;
expires 7d;
add_header Cache-Control "public, no-transform";
}
This adds browser caching headers for static assets.
Timeouts for long-running agent operations
Some Paperclip operations (agent heartbeats, long AI calls) can take several minutes. The default nginx proxy timeouts are 60 seconds, which causes 504 errors:
proxy_read_timeout 300; # 5 minutes
proxy_connect_timeout 300;
proxy_send_timeout 300;
300 seconds (5 minutes) covers the vast majority of Paperclip operations.
Testing your configuration
Always test before reloading:
sudo nginx -t
Reload without downtime:
sudo systemctl reload nginx
Check nginx error logs:
sudo tail -f /var/log/nginx/error.log
Common errors
502 Bad Gateway: Paperclip isn't running or isn't listening on 3100. Check: systemctl status paperclip and ss -tlnp | grep 3100.
504 Gateway Timeout: Operation took too long. Increase proxy_read_timeout. Check Paperclip logs for what's taking time.
101 Switching Protocols fails: WebSocket upgrade blocked. Confirm the Upgrade and Connection headers are set.
413 Request Entity Too Large: File upload exceeds client_max_body_size. Increase to 50M or 100M.
Verify WebSocket works
Open your browser's DevTools → Network tab, then open Paperclip. Look for a WebSocket connection (protocol wss://). It should show status 101 (Switching Protocols). If it shows an error, the nginx WebSocket config is missing or incorrect.
Ready to deploy?
Affiliate disclosure: this link may earn us a commission at no extra cost to you.
This is an independent guide. Paperclip Hosting is not affiliated with the official Paperclip project. Guide steps are based on real deployments and are subject to change as the software evolves.
Related articles
Deploy Paperclip on Fly.io
Run Paperclip on Fly.io with persistent storage, zero-downtime deploys, and global edge distribution — full setup guide.
Deploy Paperclip on Render
Host Paperclip on Render with a persistent disk, free SSL, and auto-deploys from GitHub — step-by-step setup guide.
Deploy Paperclip on Hetzner Cloud
Host Paperclip on Hetzner for as little as €4.51/month — one of the cheapest VPS options in Europe with great performance.
