Repository navigation
Reverse Proxy
This guide covers configuring oCMS behind popular reverse proxy servers for SSL termination, load balancing, and improved security.
- oCMS running on your server (default:
localhost:8080) - Domain name pointing to your server
- SSL certificate (Let's Encrypt recommended)
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.1oCMS 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.
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 nginxFor 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;
}
}sudo a2enmod proxy proxy_http ssl headers rewrite<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>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/letsencryptConfigure via the web UI at port 81:
- Add a Proxy Host with your domain
- Set Forward Hostname to
127.0.0.1, Port to8080 - Enable SSL, Force SSL, HTTP/2
- Add custom Nginx config for caching (see full guide above)
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
sudo systemctl enable certbot.timersudo apt install certbot python3-certbot-apache
sudo certbot --apache -d example.com -d www.example.comadd_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.
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:
- Own the domain and serve a valid HTTPS certificate on the apex and every subdomain.
- Start oCMS with
OCMS_HSTS_PRELOAD=truein production; the header becomesmax-age=31536000; includeSubDomains; preload. - Verify with
curl -sI https://example.com | grep -i strict-transport-security. - 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.
| 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 |
- Enable HTTP/2 for multiplexing
- oCMS handles gzip — avoid double compression at proxy
- Use connection keepalive for repeated requests
- Set long cache times for versioned static assets
Getting Started
Core Features
- Content Management
- Media Library
- Taxonomy
- Menu Builder
- Forms
- Multi-Language
- SEO
- Feeds
- Video Embedding
- Agent-Ready
Administration
API & Integrations
Security
Theme Development
Module Development
Built-in Modules
- Module: Sentinel
- Module: Developer
- Module: DB Manager
- Module: Migrator
- Module: Informer
- Module: Demo Mode
- Module: Internal Analytics
- Module: External Analytics
- Module: Privacy
- Module: Embed
- Module: MCP Server
Deployment