Embedded Redactor UI Deployment Reference
Redactor deployment reference
Configure same-domain and cross-domain proxy routing for embedded Redactor UI deployments. Start with the [Embedded Redactor UI tutorial](embedded-ui.md) for the end-to-end flow.
Same-Domain Proxy Configuration
When you're ready to set up your own reverse proxy instead of using the Docker Compose example, this section provides the configuration details.
The same-domain approach places the Redactor proxy under the same domain as your web application. Cookies remain first-party, so no special CORS or cookie configuration is needed.
Architecture
┌─────────────────────────────────────────────────────────┐
│ Your Domain (e.g., https://yourdomain.com) │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Web Application │ │
│ │ / # Your app HTML/JS │ │
│ │ /proxy/redactor/ # Reverse proxy to Redactor │ │
│ └─────────────────────────────────────────────────┘ │
│ │ │
│ V │
│ ┌─────────────┐ │
│ │ Redactor │ │
│ │ Server │ │
│ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
NGINX Configuration
The reverse proxy routes /proxy/redactor/ requests to the Redactor server. WebSocket support is required for real-time updates.
server {
listen 443 ssl;
server_name yourdomain.com;
ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/private.key;
# Allow large video uploads
client_max_body_size 5000M;
# Your web application
location / {
root /var/www/html;
try_files $uri /index.html;
}
# Redactor reverse proxy
location /proxy/redactor/ {
proxy_pass http://redactor:9000/;
}
# WebSocket support (required)
location /proxy/redactor/socket.io {
proxy_pass http://redactor:9000/socket.io;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
If Redactor is running on a different host or port, update the proxy_pass values accordingly.
Cross-Domain Deployment
Use the cross-domain approach when the Redactor proxy must live on a different domain than your web application. This is common when:
- Your application infrastructure doesn't allow adding proxy paths
- You want to centralize Redactor access for multiple applications
- Network architecture requires separation of concerns
Architecture
┌────────────────────────────────┐ ┌────────────────────────────────┐
│ Your App Domain │ │ Redactor Domain │
│ e.g. https://app.example.com │ │ https://redactor.example.com │
│ │ │ │
│ ┌──────────────────────────┐ │ │ ┌──────────────────────────┐ │
│ │ Web Application │ │────>│ │ NGINX Proxy │ │
│ │ (embeds Redactor) │ │ │ │ + CORS Headers │ │
│ └──────────────────────────┘ │ │ │ + Cookie Config │ │
│ │ │ └────────────┬─────────────┘ │
└────────────────────────────────┘ │ │ │
│ V │
│ ┌──────────────────────────┐ │
│ │ Redactor Server │ │
│ └──────────────────────────┘ │
└────────────────────────────────┘
NGINX Configuration
Docker Compose Example Includes a Complete Demo Environment
If you're using the provided Docker Compose example with --profile with-cross-domain-proxy, everything is included to get a working demo up and running:
- Redactor server - The core redaction service
- NGINX reverse proxy - Pre-configured with CORS headers and cookie settings for cross-domain access
- Sample web page - A separate site (simulating your application) that embeds the Redactor editor
Simply run the Docker Compose command, then:
- Open
https://127.0.0.1:3443/first and accept the self-signed certificate warning - Open
https://127.0.0.1:4443/to see the embedded editor running on a separate domain
No configuration changes are required to try the demo. For production, you'll need to update the CORS origin map to include your application's domain(s).
Cross-domain embedding requires explicit CORS headers and cookie configuration. The proxy must:
- Allow your application's origin in CORS headers
- Set cookies with
SameSite=Noneso they work cross-origin - Handle preflight OPTIONS requests for CORS
# Map trusted origins for CORS (update for your domains)
map $http_origin $cors_origin {
default "";
"https://app.example.com" "https://app.example.com";
"https://staging.example.com" "https://staging.example.com";
}
server {
listen 443 ssl;
server_name redactor.example.com;
ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/private.key;
# Reverse proxy headers
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;
# Timeouts and limits
proxy_connect_timeout 10s;
proxy_send_timeout 30s;
proxy_read_timeout 60s;
proxy_buffering off;
client_max_body_size 5000M;
# General API routes
location / {
# Replace backend CORS headers with our trusted ones
proxy_hide_header Access-Control-Allow-Origin;
proxy_hide_header Access-Control-Allow-Credentials;
# CORS preflight handling
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' "$cors_origin" always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept' always;
add_header 'Access-Control-Max-Age' 1728000 always;
add_header 'Content-Length' 0 always;
add_header 'Content-Type' 'text/plain; charset=utf-8' always;
return 204;
}
add_header 'Access-Control-Allow-Origin' "$cors_origin" always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
proxy_pass http://redactor:9000/;
# Required for cross-origin cookies
proxy_cookie_flags connect.sid Secure HttpOnly SameSite=None;
}
# File uploads (extended timeouts)
location /upload {
proxy_hide_header Access-Control-Allow-Origin;
proxy_hide_header Access-Control-Allow-Credentials;
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' "$cors_origin" always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept' always;
add_header 'Access-Control-Max-Age' 1728000 always;
add_header 'Content-Length' 0 always;
add_header 'Content-Type' 'text/plain; charset=utf-8' always;
return 204;
}
add_header 'Access-Control-Allow-Origin' "$cors_origin" always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
proxy_pass http://redactor:9000/;
# Large file uploads may take a long time
proxy_connect_timeout 10s;
proxy_send_timeout 3600s;
proxy_read_timeout 3600s;
client_max_body_size 5000M;
}
# WebSocket support
location /socket.io {
proxy_hide_header Access-Control-Allow-Origin;
proxy_hide_header Access-Control-Allow-Credentials;
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' "$cors_origin" always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept' always;
add_header 'Access-Control-Max-Age' 1728000 always;
add_header 'Content-Length' 0 always;
add_header 'Content-Type' 'text/plain; charset=utf-8' always;
return 204;
}
add_header 'Access-Control-Allow-Origin' "$cors_origin" always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
proxy_pass http://redactor:9000/socket.io;
# WebSocket upgrade headers
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_cookie_flags connect.sid Secure HttpOnly SameSite=None;
# Long-lived WebSocket connections
proxy_connect_timeout 10s;
proxy_send_timeout 30s;
proxy_read_timeout 3600s;
}
}
CORS Origin Configuration
Update the map $http_origin block to include only your trusted application domains. An empty default value ensures unknown origins are rejected.
HTML/JavaScript Integration
When embedding cross-domain, you must use absolute URLs for the Redactor script and CSS files:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Your Web App</title>
<!-- Load the Redactor script from the proxy domain -->
<script src="https://redactor.example.com/public/sighthound-redactor.js"></script>
</head>
<body>
<div id="redactor-editor" style="width: 100vw; height: 100vh;"></div>
<script>
const redactorElement = document.getElementById("redactor-editor");
const redactorServer = "https://redactor.example.com";
const redactorWhereTo = { projectId: "my-project-id" };
const redactorEditor = new redactor.default(
redactorElement,
redactorServer,
redactorWhereTo,
{
shadowed: true,
cssHref: {
// Must use absolute URLs for cross-domain
fonts: ["https://redactor.example.com/public/sighthound-redactor-fonts.css"],
main: ["https://redactor.example.com/public/sighthound-redactor.css"],
},
}
);
redactorEditor.renderEditor((err) => {
if (err) {
console.error("Editor render failed:", err);
} else {
console.log("Editor rendered successfully");
}
});
</script>
</body>
</html>
Configuration Locations
When changing your proxy domain, update these four locations:
- The
<script src="...">tag in the<head> - The
redactorServervariable in JavaScript - The
cssHref.fontsURL - The
cssHref.mainURL
Troubleshooting Cross-Domain Issues
If the embedded editor fails to load or authenticate:
- Open browser developer tools and check the Console and Network tabs for errors
- Verify CORS headers - Look for
Access-Control-Allow-Originin response headers; it should match your application's origin - Check cookie settings - The
connect.sidcookie must haveSameSite=NoneandSecureattributes - Accept self-signed certificates - During development with self-signed certs, open the proxy URL directly in your browser first and accept the security warning
Contact Support
If you encounter issues, you can find the Redactor logs at the following location in the example project. Note: you may need to sudo to access the logs:
Review the logs for error messages, or send them to [email protected] for assistance. You can also submit logs directly from within the Redactor app by following the instructions on our Support page.