← All articles
Self-hostingConfiguration

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.