Reverse Proxy
Configure Nginx, Caddy, or Traefik as a reverse proxy for oCore with TLS termination and WebSocket support.
A reverse proxy sits in front of oCore to handle TLS termination, route requests to the backend and frontend, and provide additional security layers. Choose the option that fits your infrastructure.
Architecture
Client --> Reverse Proxy (443) --> Frontend (3000)
--> Backend API (8080)
Client --> SSH Gateway (2222) [direct, not proxied]The reverse proxy handles HTTPS for the dashboard and API. The SSH gateway on port 2222 is accessed directly and does not need proxying.
Nginx
Nginx is the most widely deployed option with fine-grained control over every aspect of request handling.
Full Configuration
upstream ocore_backend {
server 127.0.0.1:8080;
keepalive 32;
}
upstream ocore_frontend {
server 127.0.0.1:3000;
keepalive 16;
}
# Redirect HTTP to HTTPS
server {
listen 80;
listen [::]:80;
server_name ocore.example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name ocore.example.com;
# TLS certificates (managed by Certbot or manually)
ssl_certificate /etc/letsencrypt/live/ocore.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ocore.example.com/privkey.pem;
# TLS configuration
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 shared:SSL:10m;
ssl_session_timeout 1d;
# Security headers
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
# Backend API
location /api/ {
proxy_pass http://ocore_backend;
proxy_http_version 1.1;
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;
# WebSocket support (for SSE/streaming endpoints)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Increase timeouts for long-running operations
proxy_read_timeout 300s;
proxy_send_timeout 300s;
# Disable buffering for streaming responses
proxy_buffering off;
}
# Frontend dashboard
location / {
proxy_pass http://ocore_frontend;
proxy_http_version 1.1;
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;
# Next.js hot reload (development only)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# Max upload size (for backup uploads, etc.)
client_max_body_size 512M;
}Enable the Site
sudo ln -s /etc/nginx/sites-available/ocore /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginxCaddy
Caddy automatically obtains and renews TLS certificates from Let's Encrypt. It is the simplest option for most deployments.
Caddyfile
ocore.example.com {
# Backend API
handle /api/* {
reverse_proxy localhost:8080 {
header_up X-Real-IP {remote_host}
header_up X-Forwarded-Proto {scheme}
# Flush immediately for SSE/streaming
flush_interval -1
}
}
# Frontend dashboard
handle {
reverse_proxy localhost:3000 {
header_up X-Real-IP {remote_host}
header_up X-Forwarded-Proto {scheme}
}
}
# Security headers
header {
X-Frame-Options DENY
X-Content-Type-Options nosniff
Referrer-Policy strict-origin-when-cross-origin
Strict-Transport-Security "max-age=63072000; includeSubDomains"
}
}Start Caddy
sudo systemctl enable caddy
sudo systemctl start caddyCaddy automatically obtains a TLS certificate for ocore.example.com on first request. No additional certificate configuration is needed.
Traefik
Traefik integrates natively with Docker and configures routing through container labels.
Static Configuration
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
certificatesResolvers:
letsencrypt:
acme:
email: admin@example.com
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: web
providers:
docker:
exposedByDefault: falseDocker Labels
Add labels to your docker-compose.prod.yml services:
services:
backend:
# ... existing config
labels:
- "traefik.enable=true"
- "traefik.http.routers.ocore-api.rule=Host(`ocore.example.com`) && PathPrefix(`/api`)"
- "traefik.http.routers.ocore-api.entrypoints=websecure"
- "traefik.http.routers.ocore-api.tls.certresolver=letsencrypt"
- "traefik.http.services.ocore-api.loadbalancer.server.port=8080"
frontend:
# ... existing config
labels:
- "traefik.enable=true"
- "traefik.http.routers.ocore-frontend.rule=Host(`ocore.example.com`)"
- "traefik.http.routers.ocore-frontend.entrypoints=websecure"
- "traefik.http.routers.ocore-frontend.tls.certresolver=letsencrypt"
- "traefik.http.services.ocore-frontend.loadbalancer.server.port=3000"
# Lower priority so /api routes match the backend first
- "traefik.http.routers.ocore-frontend.priority=1"
traefik:
image: traefik:v3
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- traefik_letsencrypt:/letsencrypt
restart: unless-stopped
volumes:
traefik_letsencrypt:
name: traefik_letsencryptChoosing a Reverse Proxy
| Feature | Nginx | Caddy | Traefik |
|---|---|---|---|
| Automatic TLS | No (use Certbot) | Yes | Yes |
| Configuration | File-based | File-based | Docker labels |
| WebSocket support | Manual config | Built-in | Built-in |
| Learning curve | Moderate | Low | Moderate |
| Resource usage | Low | Low | Low-Moderate |
| Best for | Existing Nginx infrastructure | New deployments, simplicity | Docker-native environments |
Recommendation: Use Caddy for new deployments -- automatic TLS with zero certificate management. Use Nginx if you already have it in your stack. Use Traefik if you run multiple Docker services and want label-based routing.
Verifying the Proxy
After configuring your reverse proxy, verify it is working:
# Check HTTPS is working
curl -I https://ocore.example.com
# Check the API is accessible
curl https://ocore.example.com/api/health
# Check security headers
curl -I https://ocore.example.com | grep -E "Strict-Transport|X-Frame|X-Content"