Skip to content

Reverse Proxy Configuration Guide for Readur

This guide covers various deployment scenarios for running Readur behind reverse proxies with configurable ports.

Table of Contents

Port Configuration

Readur supports flexible port configuration through environment variables:

Server Port Configuration

# Method 1: Full address specification
SERVER_ADDRESS=0.0.0.0:3000

# Method 2: Separate host and port (recommended)
SERVER_HOST=0.0.0.0
SERVER_PORT=3000

Development Port Configuration

For frontend development with Vite:

# Backend API port
BACKEND_PORT=8000

# Frontend dev server port
CLIENT_PORT=5173

Docker Deployment

Basic Docker Run

# Run on custom port 3000
docker run -d \
  -e SERVER_PORT=3000 \
  -e DATABASE_URL=postgresql://user:pass@host/db \
  -e JWT_SECRET=your-secret-key \
  -p 3000:3000 \
  readur:latest

Docker Compose with Custom Ports

version: '3.8'

services:
  readur:
    image: readur:latest
    environment:
      SERVER_PORT: 3000
      DATABASE_URL: postgresql://readur:readur@postgres/readur
      JWT_SECRET: ${JWT_SECRET}
    ports:
      - "3000:3000"

Nginx Configuration

Basic Reverse Proxy

server {
    listen 80;
    server_name readur.example.com;

    location / {
        proxy_pass http://localhost:3000;
        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;

        # Large file uploads for documents
        client_max_body_size 100M;

        # Extended timeouts for OCR processing
        proxy_connect_timeout 300s;
        proxy_send_timeout 300s;
        proxy_read_timeout 300s;
    }
}

SSL Configuration

server {
    listen 443 ssl http2;
    server_name readur.example.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    location / {
        proxy_pass http://localhost:3000;
        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 https;

        # WebSocket support (future feature)
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Subpath Configuration

To serve Readur under a subpath like /readur/:

location /readur/ {
    # Remove prefix when passing to backend
    rewrite ^/readur/(.*) /$1 break;

    proxy_pass http://localhost:3000;
    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-Prefix /readur;
}

Traefik Configuration

Docker Labels

version: '3.8'

services:
  readur:
    image: readur:latest
    environment:
      SERVER_PORT: 3000
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.readur.rule=Host(`readur.example.com`)"
      - "traefik.http.routers.readur.tls=true"
      - "traefik.http.routers.readur.tls.certresolver=letsencrypt"
      - "traefik.http.services.readur.loadbalancer.server.port=3000"
      # Middleware for large uploads
      - "traefik.http.middlewares.readur-upload.buffering.maxRequestBodyBytes=104857600"
      - "traefik.http.routers.readur.middlewares=readur-upload"

Static Configuration

# traefik.yml
http:
  routers:
    readur:
      rule: "Host(`readur.example.com`)"
      service: readur
      tls:
        certResolver: letsencrypt
      middlewares:
        - readur-headers

  services:
    readur:
      loadBalancer:
        servers:
          - url: "http://localhost:3000"
        healthCheck:
          path: /api/health
          interval: 30s

  middlewares:
    readur-headers:
      headers:
        customRequestHeaders:
          X-Forwarded-Proto: https

Apache Configuration

Basic Reverse Proxy

<VirtualHost *:80>
    ServerName readur.example.com

    ProxyRequests Off
    ProxyPreserveHost On

    ProxyPass / http://localhost:3000/
    ProxyPassReverse / http://localhost:3000/

    # Large file uploads
    LimitRequestBody 104857600

    # Extended timeouts
    ProxyTimeout 300
</VirtualHost>

SSL Configuration

<VirtualHost *:443>
    ServerName readur.example.com

    SSLEngine on
    SSLCertificateFile /path/to/cert.pem
    SSLCertificateKeyFile /path/to/key.pem

    ProxyRequests Off
    ProxyPreserveHost On

    ProxyPass / http://localhost:3000/
    ProxyPassReverse / http://localhost:3000/

    RequestHeader set X-Forwarded-Proto "https"
</VirtualHost>

Caddy Configuration

Caddyfile

readur.example.com {
    reverse_proxy localhost:3000 {
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-Proto {scheme}

        # Health check
        health_uri /api/health
        health_interval 30s

        # Extended timeouts
        transport http {
            dial_timeout 10s
            response_header_timeout 300s
        }
    }

    # Large file uploads
    request_body {
        max_size 100MB
    }
}

Common Scenarios

Multiple Instances with Load Balancing

Nginx

upstream readur_pool {
    least_conn;
    server readur1:3000 max_fails=3 fail_timeout=30s;
    server readur:3000 max_fails=3 fail_timeout=30s;
    server readur3:3000 max_fails=3 fail_timeout=30s;
}

server {
    location / {
        proxy_pass http://readur_pool;
        # ... other proxy settings
    }
}

Docker Compose Scale

# Scale to 3 instances
docker compose up -d --scale readur=3

Blue-Green Deployment

# nginx.conf
upstream readur_blue {
    server blue:3000;
}

upstream readur_green {
    server green:3000;
}

# Switch between blue and green
upstream readur_current {
    server blue:3000;  # Change to green:3000 for switch
}

server {
    location / {
        proxy_pass http://readur_current;
    }
}

Rate Limiting

# Define rate limit zones
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=auth:10m rate=5r/m;

server {
    # Apply to API endpoints
    location /api/ {
        limit_req zone=api burst=20 nodelay;
        proxy_pass http://localhost:3000;
    }

    # Stricter for auth
    location /api/auth/ {
        limit_req zone=auth burst=5 nodelay;
        proxy_pass http://localhost:3000;
    }
}

Troubleshooting

Common Issues

  1. 502 Bad Gateway
  2. Check if Readur is running on the configured port
  3. Verify SERVER_PORT environment variable
  4. Check container logs: docker logs readur

  5. Large File Upload Failures

  6. Increase client_max_body_size in nginx
  7. Adjust MAX_FILE_SIZE_MB in Readur config
  8. Check proxy timeout settings

  9. WebSocket Connection Issues (future feature)

  10. Ensure proxy_http_version 1.1 is set
  11. Include Upgrade and Connection headers

  12. CORS Errors

  13. Readur includes CORS middleware
  14. Ensure X-Forwarded-Proto header is set correctly
  15. Check if frontend URL matches expected origin

Health Check Monitoring

# Direct health check
curl http://localhost:3000/api/health

# Through reverse proxy
curl https://readur.example.com/api/health

# Expected response
{"status":"ok"}

Debug Headers

Add these to your reverse proxy for debugging:

add_header X-Proxy-Debug "nginx" always;
add_header X-Upstream-Addr $upstream_addr always;
add_header X-Upstream-Status $upstream_status always;

Security Recommendations

  1. Always use HTTPS in production
  2. Implement rate limiting to prevent abuse
  3. Set security headers:

    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 "strict-origin-when-cross-origin" always;
    

  4. Restrict access to health check endpoint if needed:

    location /api/health {
        allow 10.0.0.0/8;
        deny all;
        proxy_pass http://localhost:3000;
    }
    

  5. Use strong JWT secrets and rotate them regularly

  6. Monitor access logs for suspicious activity

Performance Optimization

  1. Enable caching for static assets:

    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
        proxy_pass http://localhost:3000;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
    

  2. Use HTTP/2 for better performance

  3. Enable gzip compression:

    gzip on;
    gzip_types text/plain text/css application/json application/javascript;
    gzip_min_length 1000;
    

  4. Configure connection pooling for database

  5. Use CDN for static assets in production