Overview
A reverse proxy sits between clients and your Documenso instance, providing:
- SSL/TLS termination - Handle HTTPS encryption
- Security headers - Add security headers to responses
- Rate limiting - Protect against abuse
- Compression - Reduce bandwidth usage
- Load balancing - Distribute traffic across multiple instances
Nginx Configuration
Prerequisites
Install Nginx:
# Ubuntu/Debian
sudo apt update
sudo apt install nginx
# CentOS/RHEL
sudo yum install nginx
# macOS
brew install nginx
Verify installation:
Basic Configuration
Create a new site configuration:
# /etc/nginx/sites-available/documenso
server {
listen 80;
server_name sign.example.com;
# Redirect HTTP to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name sign.example.com;
# SSL Configuration (see SSL Certificates section)
ssl_certificate /etc/ssl/certs/documenso.crt;
ssl_certificate_key /etc/ssl/private/documenso.key;
# Proxy to Documenso
location / {
proxy_pass http://localhost:3000;
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;
}
}
Enable the site:
sudo ln -s /etc/nginx/sites-available/documenso /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Production Configuration
Enhanced configuration with security and performance optimizations:
# /etc/nginx/sites-available/documenso
server {
listen 80;
listen [::]:80;
server_name sign.example.com;
# Redirect all HTTP to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name sign.example.com;
# SSL Configuration
ssl_certificate /etc/letsencrypt/live/sign.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/sign.example.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/sign.example.com/chain.pem;
# SSL Protocols and Ciphers
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers off;
# SSL Session Cache
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# Security Headers
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
# Client Body Size (for document uploads)
client_max_body_size 50M;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
# Gzip Compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml;
# Proxy Configuration
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
# WebSocket support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Standard proxy headers
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_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
# Disable buffering for real-time updates
proxy_buffering off;
proxy_cache_bypass $http_upgrade;
}
# Health check endpoint (optional)
location /api/health {
proxy_pass http://localhost:3000/api/health;
access_log off;
}
# Access and Error Logs
access_log /var/log/nginx/documenso-access.log;
error_log /var/log/nginx/documenso-error.log;
}
Multiple Instances (Load Balancing)
For high availability, distribute traffic across multiple Documenso instances:
# /etc/nginx/sites-available/documenso
upstream documenso_backend {
least_conn;
server localhost:3001 max_fails=3 fail_timeout=30s;
server localhost:3002 max_fails=3 fail_timeout=30s;
server localhost:3003 max_fails=3 fail_timeout=30s;
keepalive 32;
}
server {
listen 443 ssl http2;
server_name sign.example.com;
# SSL configuration...
location / {
proxy_pass http://documenso_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
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;
}
}
Rate Limiting
Protect against abuse and DoS attacks:
# Add to /etc/nginx/nginx.conf in http block
http {
# Rate limiting zones
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;
limit_req_zone $binary_remote_addr zone=auth:10m rate=5r/m;
# Connection limiting
limit_conn_zone $binary_remote_addr zone=addr:10m;
}
# In your server block
server {
# General rate limit
limit_req zone=general burst=20 nodelay;
limit_conn addr 10;
# API endpoints
location /api/ {
limit_req zone=api burst=50 nodelay;
proxy_pass http://localhost:3000;
}
# Auth endpoints (stricter limits)
location /api/auth/ {
limit_req zone=auth burst=5 nodelay;
proxy_pass http://localhost:3000;
}
}
Caddy Configuration
Prerequisites
Install Caddy:
# Ubuntu/Debian
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy
# macOS
brew install caddy
Verify installation:
Basic Configuration
Caddy automatically handles SSL certificates via Let’s Encrypt:
# /etc/caddy/Caddyfile
sign.example.com {
reverse_proxy localhost:3000
}
Reload configuration:
sudo systemctl reload caddy
Caddy automatically obtains and renews SSL certificates from Let’s Encrypt. No manual certificate management required!
Production Configuration
Enhanced configuration with all production features:
# /etc/caddy/Caddyfile
{
# Global options
email admin@example.com
# Use Let's Encrypt production
acme_ca https://acme-v02.api.letsencrypt.org/directory
# Enable admin API
admin localhost:2019
}
sign.example.com {
# Automatic HTTPS with Let's Encrypt
# Enable compression
encode gzip zstd
# Security headers
header {
Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
X-XSS-Protection "1; mode=block"
Referrer-Policy "no-referrer-when-downgrade"
-Server
}
# Request body size limit (50MB for document uploads)
request_body {
max_size 50MB
}
# Logging
log {
output file /var/log/caddy/documenso.log {
roll_size 100mb
roll_keep 10
roll_keep_for 720h
}
format json
}
# Reverse proxy to Documenso
reverse_proxy localhost:3000 {
# Health check
health_uri /api/health
health_interval 30s
health_timeout 5s
# Timeouts
transport http {
dial_timeout 5s
response_header_timeout 300s
}
}
}
Multiple Instances (Load Balancing)
sign.example.com {
encode gzip zstd
# Load balancing across multiple instances
reverse_proxy localhost:3001 localhost:3002 localhost:3003 {
# Load balancing policy
lb_policy least_conn
# Health checks
health_uri /api/health
health_interval 10s
health_timeout 5s
# Retry failed requests
lb_try_duration 5s
lb_try_interval 250ms
}
}
Rate Limiting
Caddy rate limiting using the rate-limit plugin:
sign.example.com {
# General rate limit
rate_limit {
zone general {
key {remote_host}
events 100
window 1m
}
}
# API endpoints - higher limit
@api {
path /api/*
}
route @api {
rate_limit {
zone api {
key {remote_host}
events 300
window 1m
}
}
reverse_proxy localhost:3000
}
# Auth endpoints - strict limit
@auth {
path /api/auth/*
}
route @auth {
rate_limit {
zone auth {
key {remote_host}
events 10
window 1m
}
}
reverse_proxy localhost:3000
}
# Default route
reverse_proxy localhost:3000
}
The rate_limit directive requires the Caddy rate-limit plugin. Install it with: caddy add-package github.com/mholt/caddy-ratelimit
Custom SSL Certificates
If you want to use your own SSL certificates instead of Let’s Encrypt:
sign.example.com {
tls /path/to/cert.pem /path/to/key.pem
reverse_proxy localhost:3000
}
Traefik Configuration
Docker Compose with Traefik
Integrate Documenso with Traefik for automatic SSL:
version: '3.8'
services:
traefik:
image: traefik:v2.10
container_name: traefik
command:
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- traefik-letsencrypt:/letsencrypt
restart: unless-stopped
database:
image: postgres:15
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
volumes:
- database:/var/lib/postgresql/data
restart: unless-stopped
documenso:
image: documenso/documenso:latest
depends_on:
- database
environment:
- NEXT_PUBLIC_WEBAPP_URL=https://sign.example.com
# ... other environment variables
volumes:
- /opt/documenso/cert.p12:/opt/documenso/cert.p12:ro
labels:
- "traefik.enable=true"
# HTTP to HTTPS redirect
- "traefik.http.routers.documenso-http.rule=Host(`sign.example.com`)"
- "traefik.http.routers.documenso-http.entrypoints=web"
- "traefik.http.routers.documenso-http.middlewares=redirect-to-https"
# HTTPS router
- "traefik.http.routers.documenso.rule=Host(`sign.example.com`)"
- "traefik.http.routers.documenso.entrypoints=websecure"
- "traefik.http.routers.documenso.tls=true"
- "traefik.http.routers.documenso.tls.certresolver=letsencrypt"
# Service configuration
- "traefik.http.services.documenso.loadbalancer.server.port=3000"
# Middlewares
- "traefik.http.routers.documenso.middlewares=security-headers"
# Redirect middleware
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
- "traefik.http.middlewares.redirect-to-https.redirectscheme.permanent=true"
# Security headers
- "traefik.http.middlewares.security-headers.headers.stsSeconds=63072000"
- "traefik.http.middlewares.security-headers.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.security-headers.headers.stsPreload=true"
- "traefik.http.middlewares.security-headers.headers.frameDeny=true"
- "traefik.http.middlewares.security-headers.headers.contentTypeNosniff=true"
restart: unless-stopped
volumes:
database:
traefik-letsencrypt:
Testing Your Configuration
Verify Proxy is Working
Test that Documenso is accessible through the proxy:
curl -I https://sign.example.com
Check SSL Configuration
Use SSL Labs to test your SSL configuration:
https://www.ssllabs.com/ssltest/analyze.html?d=sign.example.com
Check that security headers are present:
curl -I https://sign.example.com | grep -i "strict-transport\|x-frame\|x-content"
Test Health Endpoint
Verify the health check endpoint:
curl https://sign.example.com/api/health
Expected response:
{
"status": "ok",
"timestamp": "2024-03-04T12:00:00.000Z"
}
Troubleshooting
502 Bad Gateway
Causes:
- Documenso container not running
- Wrong proxy_pass port
- Firewall blocking connection
Solution:
# Check Documenso is running
docker compose ps
# Test local connection
curl http://localhost:3000/api/health
# Check Nginx error logs
sudo tail -f /var/log/nginx/error.log
413 Request Entity Too Large
Increase client body size:
# Nginx
client_max_body_size 50M;
# Caddy
request_body {
max_size 50MB
}
SSL Certificate Errors
For Nginx with Let’s Encrypt:
# Renew certificates
sudo certbot renew --nginx
# Test renewal
sudo certbot renew --dry-run
For Caddy:
# Check Caddy logs
sudo journalctl -u caddy -f
# Verify certificate
caddy trust
WebSocket Connection Failed
Ensure WebSocket headers are set:
# Nginx
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Caddy handles WebSockets automatically
reverse_proxy localhost:3000
See Also