Skip to content

Reverse Proxy

Oleg Ivanchenko edited this page Oct 18, 2026 · 5 revisions

Reverse Proxy

This guide covers configuring oCMS behind popular reverse proxy servers for SSL termination, load balancing, and improved security.

Prerequisites

  1. oCMS running on your server (default: localhost:8080)
  2. Domain name pointing to your server
  3. SSL certificate (Let's Encrypt recommended)

Environment Configuration

export OCMS_SERVER_HOST=127.0.0.1
export OCMS_SERVER_PORT=8080
export OCMS_ENV=production
# Trust forwarding headers only from the proxy (IPs or CIDRs, comma-separated)
export OCMS_TRUSTED_PROXIES=127.0.0.1

oCMS honors the client-address headers X-Forwarded-For and X-Real-IP, and the X-Forwarded-Host and X-Forwarded-Proto headers used to resolve a page's public origin, only when the connection comes from an address in OCMS_TRUSTED_PROXIES. Without it every visitor appears with the proxy's address, so rate limiting, account lockout, analytics, hCaptcha and GeoIP all see the proxy instead of the client. With OCMS_ENV=production, startup fails while it is unset unless OCMS_REQUIRE_TRUSTED_PROXIES=false.

Nginx

Basic Configuration

Create /etc/nginx/sites-available/ocms:

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$server_name$request_uri;
}

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

    # SSL
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;

    client_max_body_size 100M;

    # Proxy headers (keep them all at this level: a location that sets its own
    # proxy_set_header directives stops inheriting the ones from here)
    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 Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

    proxy_connect_timeout 60s;
    proxy_send_timeout 60s;
    proxy_read_timeout 60s;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
    }

    # Static assets caching
    location /static/ {
        proxy_pass http://127.0.0.1:8080;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    location /themes/ {
        proxy_pass http://127.0.0.1:8080;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    location /uploads/ {
        proxy_pass http://127.0.0.1:8080;
        expires 30d;
        add_header Cache-Control "public";
    }

    location /health {
        proxy_pass http://127.0.0.1:8080;
        access_log off;
    }

    location ~ /\. { deny all; }
}

Enable:

sudo ln -s /etc/nginx/sites-available/ocms /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Load Balancing

For multiple oCMS instances (each keeps its own in-memory caches; they are not shared through Redis, see Caching). Content imports stage the uploaded file on the instance that received it, so route one administrator session to the same instance (for example ip_hash or a cookie-based sticky directive) or run imports against a single instance:

upstream ocms_backend {
    least_conn;
    server 127.0.0.1:8080;
    server 127.0.0.1:8081;
    server 127.0.0.1:8082 backup;
    keepalive 32;
}

server {
    listen 443 ssl http2;
    server_name example.com;
    # ... SSL config ...

    location / {
        proxy_pass http://ocms_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;
    }
}

Apache

Enable Modules

sudo a2enmod proxy proxy_http ssl headers rewrite

Configuration

<VirtualHost *:80>
    ServerName example.com
    RewriteEngine On
    RewriteCond %{HTTPS} off
    RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</VirtualHost>

<VirtualHost *:443>
    ServerName example.com

    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
    SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1

    ProxyPreserveHost On
    ProxyRequests Off
    RequestHeader set X-Real-IP "%{REMOTE_ADDR}s"
    RequestHeader set X-Forwarded-Proto "https"

    ProxyPass / http://127.0.0.1:8080/
    ProxyPassReverse / http://127.0.0.1:8080/
    ProxyTimeout 60

    <LocationMatch "^/(static|themes)/">
        Header set Cache-Control "public, max-age=31536000, immutable"
    </LocationMatch>

    <Location /uploads/>
        Header set Cache-Control "public, max-age=2592000"
    </Location>
</VirtualHost>

Nginx Proxy Manager

For a GUI-based approach using Docker:

services:
  nginx-proxy-manager:
    image: 'jc21/nginx-proxy-manager:latest'
    restart: unless-stopped
    ports:
      - '80:80'
      - '443:443'
      - '81:81'
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt

Configure via the web UI at port 81:

  1. Add a Proxy Host with your domain
  2. Set Forward Hostname to 127.0.0.1, Port to 8080
  3. Enable SSL, Force SSL, HTTP/2
  4. Add custom Nginx config for caching (see full guide above)

SSL with Let's Encrypt

Nginx

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
sudo systemctl enable certbot.timer

Apache

sudo apt install certbot python3-certbot-apache
sudo certbot --apache -d example.com -d www.example.com

Security Headers

add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

Note: oCMS already sets security headers (CSP, HSTS, etc.). Avoid duplicating headers between the proxy and application.

HSTS Preload

oCMS emits Strict-Transport-Security: max-age=31536000; includeSubDomains in production. This protects returning visitors but leaves new visitors exposed on their first connection. The HSTS preload list closes that gap — major browsers ship with it baked in.

To submit:

  1. Own the domain and serve a valid HTTPS certificate on the apex and every subdomain.
  2. Start oCMS with OCMS_HSTS_PRELOAD=true in production; the header becomes max-age=31536000; includeSubDomains; preload.
  3. Verify with curl -sI https://example.com | grep -i strict-transport-security.
  4. Submit at https://hstspreload.org.

Warning: preload is effectively permanent. Removal takes weeks and older browsers keep pinning HTTPS for the original max-age. Do not enable unless every subdomain has TLS.

Troubleshooting

Issue Cause Solution
502 Bad Gateway oCMS not running Check curl http://127.0.0.1:8080/health
504 Gateway Timeout Slow response Increase proxy timeout values
413 Request Entity Too Large Upload too big Increase client_max_body_size
Mixed Content Warnings Missing X-Forwarded-Proto, or proxy not in OCMS_TRUSTED_PROXIES Ensure proxy passes HTTPS scheme and is trusted; set site_url to the https:// origin

Performance Tips

  1. Enable HTTP/2 for multiplexing
  2. oCMS handles gzip — avoid double compression at proxy
  3. Use connection keepalive for repeated requests
  4. Set long cache times for versioned static assets

Clone this wiki locally