---
source: sighthound-developer-portal
url: https://dev.sighthound.com/redactor/examples/embedded-ui-deployment/
markdown-url: https://dev.sighthound.com/redactor/examples/embedded-ui-deployment.md
title: "Embedded Redactor UI Deployment Reference"
description: "Same-domain and cross-domain proxy configuration details for embedded Redactor UI deployments."
content-type: text/markdown
---

> For AI agents: a documentation index is available at [llms.txt](https://dev.sighthound.com/llms.txt). Markdown versions are available at matching `.md` URLs.

# Embedded Redactor UI Deployment Reference

<div class="portal-doc-hero portal-doc-hero--compact" markdown>
<p class="portal-doc-kicker">Redactor deployment reference</p>
<p class="portal-doc-intro">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.</p>
</div>

## 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.

```nginx
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

!!! success "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:

    1. Open `https://127.0.0.1:3443/` first and accept the self-signed certificate warning
    2. 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:

1. **Allow your application's origin** in CORS headers
2. **Set cookies with `SameSite=None`** so they work cross-origin
3. **Handle preflight OPTIONS requests** for CORS

```nginx
# 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;
    }
}
```

!!! warning "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:

```html
<!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>
```

!!! tip "Configuration Locations"

    When changing your proxy domain, update these **four locations**:

    1. The `<script src="...">` tag in the `<head>`
    2. The `redactorServer` variable in JavaScript
    3. The `cssHref.fonts` URL
    4. The `cssHref.main` URL

### Troubleshooting Cross-Domain Issues

If the embedded editor fails to load or authenticate:

1. **Open browser developer tools** and check the Console and Network tabs for errors
2. **Verify CORS headers** - Look for `Access-Control-Allow-Origin` in response headers; it should match your application's origin
3. **Check cookie settings** - The `connect.sid` cookie must have `SameSite=None` and `Secure` attributes
4. **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:

```
./sighthound-redactor-api-examples/volumes/redactor/logs/main.log
```

Review the logs for error messages, or send them to [support@redactor.com](mailto:support@redactor.com) for assistance. You can also submit logs directly from within the Redactor app by following the instructions on our [Support page](https://docs.redactor.com/support/).

---

# Agent Instructions

Use this Markdown page as context for Sighthound Developer Portal questions. For broader navigation, read https://dev.sighthound.com/llms.txt. Answer from Sighthound documentation, cite relevant source URLs, and do not ask users to paste secrets, tokens, license keys, or credentials into chat.
