# Fastly

This guide explains how to configure Fastly Compute\@Edge to create a reverse proxy that serves the Didomi Consent notice from your own domain and a subdomain. Two implementation options are available based on your requirements.

[Choose Your Implementation](#choose-your-implementation)

[Implementation guide](#implementation-guide)

## Choose Your Implementation

### Option A: Use a subdomain

To implement a reverse proxy on a subdomain, you will first create a lightweight Rust application compiled to WebAssembly, then configure Fastly backends and deploy the WASM binary. This approach uses minimal edge processing with simple backend routing.

<figure><img src="https://1703900661-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LDh8ZWDZrXs8sc4QKEQ%2Fuploads%2Fgit-blob-7593cc61c6c719f5f860703cb97fc29bce23b3ae%2FServer-side%20Setup%20Miro%20(1).jpg?alt=media" alt=""><figcaption></figcaption></figure>

* **Customer Usage**: `/api/*` and `/sdk/*` paths directly
* **Architecture**: Fastly with minimal WASM processing
* **Implementation**: Simple backend routing with lightweight Rust code

### Option B: Use the main domain

To implement a reverse proxy on the main domain, you will first create a Rust application with URL transformation logic, then compile it to WebAssembly and deploy to Fastly Compute\@Edge. The application handles `/consent/*` prefix removal and routes requests to appropriate Didomi backends.

<figure><img src="https://1703900661-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LDh8ZWDZrXs8sc4QKEQ%2Fuploads%2Fgit-blob-8e5990a6a6396588e5fb3049d53553a9ae35478a%2FServer-side%20Setup%20Miro.jpg?alt=media" alt=""><figcaption></figcaption></figure>

* **Customer Usage**: `/consent/*` prefix for all CMP requests
* **Architecture**: Fastly Compute\@Edge with full URL transformation
* **Implementation**: URL transformation and advanced processing

### Domain vs Subdomain Trade-offs

When implementing a reverse proxy for the Didomi SDK and its API events, you need to choose between using your main domain or a dedicated subdomain. This choice has important implications for Safari's cookie restrictions.

For more information, see this [trade-off matrix](https://developers.didomi.io/api-and-platform/domains/reverse-proxy/..#domain-vs-subdomain-implementation) to select the implementation that suits your requirements.

## Implementation guide

[Shared setup steps (both options)](#shared-setup-steps-both-options)

[Option A: Use a subdomain](#option-a-use-a-subdomain)

[Option B: Use the main domain](#option-b-use-the-main-domain)

### Common prerequisites (Both options)

* Fastly account with Compute\@Edge enabled
* Rust toolchain with WebAssembly support
* `fastly` CLI tool installed ([installing and configuring Fastly CLI](https://www.fastly.com/documentation/reference/tools/cli/))
* Domain configured for Fastly service
* Access to your domain's DNS configuration

### Shared setup steps (both options)

#### Domain and DNS configuration

#### 1. Domain setup in Fastly

#### Add domains

1. **Log into Fastly Dashboard**
2. **Navigate to**: Configure → Domains
3. **Add Domains**: Enter both `YOUR_DOMAIN_NAME` and `www.YOUR_DOMAIN_NAME`

#### 2. DNS configuration

#### For root domain (A record)

```bash
# Configure A records pointing to Fastly IP addresses
# Get current Fastly IP addresses from: <https://docs.fastly.com/en/guides/accessing-fastlys-ip-ranges>

# Example A records (verify current IPs with Fastly):
YOUR_DOMAIN_NAME.     300    IN    A    151.101.1.140
YOUR_DOMAIN_NAME.     300    IN    A    151.101.65.140
YOUR_DOMAIN_NAME.     300    IN    A    151.101.129.140
YOUR_DOMAIN_NAME.     300    IN    A    151.101.193.140
```

#### For subdomain (CNAME Record)

```bash
# Configure CNAME record for www subdomain
www.YOUR_DOMAIN_NAME.     300    IN    CNAME    YOUR_DOMAIN_NAME.
```

#### 3. TLS Certificate configuration

![](https://1703900661-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LDh8ZWDZrXs8sc4QKEQ%2Fuploads%2Fgit-blob-321b98a4e0c5848a863dc86d1576e70e29c61cb7%2FScreenshot_2025-07-23_at_5.16.46_PM.png?alt=media)

#### Certificate subscription setup

1. **Navigate to**: TLS Configuration → Certificates
2. **Create new subscription**
3. **Configure subscription**:
   * **Domains**: Enter `YOUR_DOMAIN_NAME, www.YOUR_DOMAIN_NAME` (comma-separated)
   * **Common Name**: `YOUR_DOMAIN_NAME`
   * **Certification Authority**: Let's Encrypt
   * **TLS Configuration**: `HTTP/3 & TLS v1.3 + 0RTT (t.sni)`

#### ACME challenge configuration

After submitting the certificate request, Fastly will provide an ACME challenge:

1. **Create DNS CNAME record**:

   ```bash
   # Example provided by Fastly (replace with your actual values)
   _acme-challenge.YOUR_DOMAIN_NAME    CNAME    YOUR_CHALLENGE_TOKEN.fastly-validations.com
   ```
2. **Verify DNS propagation**:

   ```bash
   # Verify the ACME challenge CNAME is propagated
   dig _acme-challenge.YOUR_DOMAIN_NAME CNAME +short
   # Should return: YOUR_CHALLENGE_TOKEN.fastly-validations.com
   ```
3. **Certificate validation**: Fastly will automatically validate domain ownership and issue the certificate

#### Fastly service configuration

#### 1. Create Fastly service

Create a new Compute\@Edge service in Fastly dashboard or via CLI:

```bash
fastly compute init --from=https://github.com/fastly/compute-starter-kit-rust-default
```

#### 2. Configure backends (Both options use same backends)

![](https://1703900661-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LDh8ZWDZrXs8sc4QKEQ%2Fuploads%2Fgit-blob-61b0d62b20f98830e4c8b94dbdf4098a2a29809f%2F_Screenshot_2025-07-23_at_5.15.34_PM.png?alt=media)

In the Fastly dashboard, configure two backends:

**Backend 1: Didomi SDK**

* **Name**: `didomi_sdk`
* **Address**: `sdk.privacy-center.org`
* **Port**: `443` (HTTPS)
* **Host Header**: `sdk.privacy-center.org`
* **Override Host**: Yes
* **Use SSL**: Yes
* **SSL SNI Hostname**: `sdk.privacy-center.org`
* **SSL Certificate Hostname**: `sdk.privacy-center.org`

**Backend 2: Didomi API**

* **Name**: `didomi_api`
* **Address**: `api.privacy-center.org`
* **Port**: `443` (HTTPS)
* **Host Header**: `api.privacy-center.org`
* **Override Host**: Yes
* **Use SSL**: Yes
* **SSL SNI Hostname**: `api.privacy-center.org`
* **SSL Certificate Hostname**: `api.privacy-center.org`

***

### Option A: Use a subdomain

This option uses simple direct routing with minimal WASM code on a subdomain.

#### Step 1: Create [fastly.toml](https://github.com/didomi/boilerplate-fastly-reverse-proxy-didomi-cmp/blob/main/fastly.toml)

```toml
# This file describes a Fastly Compute package. To learn more visit:
# <https://www.fastly.com/documentation/reference/compute/fastly-toml>

authors = ["didomi-team"]
description = "Boilerplate Fastly Reverse Proxy for Didomi CMP"
language = "rust"
manifest_version = 3
name = "boilerplate-fastly-reverse-proxy-didomi-cmp"
service_id = "{{YOUR_SERVICE_ID}}"

[local_server]

  [local_server.backends]

    [local_server.backends.didomi_api]
      url = "<https://api.privacy-center.org>"

    [local_server.backends.didomi_sdk]
      url = "<https://sdk.privacy-center.org>"

[scripts]
  build = "    cargo build --bin boilerplate-fastly-reverse-proxy-didomi-cmp --release --target wasm32-wasip1 --color always\\n"

```

#### Step 2: Create [Cargo.toml](https://github.com/didomi/boilerplate-fastly-reverse-proxy-didomi-cmp/blob/main/Cargo.toml)

```toml
[package]
name = "boilerplate-fastly-reverse-proxy-didomi-cmp"
version = "0.1.0"
edition = "2021"

[dependencies]
chrono = "0.4.41"
fastly = "0.11.5"
fern = "0.7.1"
log = "0.4"
log-fastly = "0.11.5"

```

#### Step 3: Create simple implementation ([src/main\_simplified.rs](https://github.com/didomi/boilerplate-fastly-reverse-proxy-didomi-cmp/blob/main/src/main_simplified.rs))

```rust
use fastly::http::{header, Method, StatusCode};
use fastly::{Backend, Error, Request, Response};

mod template;

#[fastly::main]
fn main(req: Request) -> Result<Response, Error> {
    let path = req.get_path();
    
    match req.get_method() {
        &Method::GET | &Method::POST => {
            if path.starts_with("/api/") {
                proxy_to_backend(req, "didomi_api")
            } else if path.starts_with("/sdk/") {
                proxy_to_backend(req, "didomi_sdk")
            } else if path == "/" {
                // Serve UI template for root path
                Ok(Response::from_status(StatusCode::OK)
                    .with_body(template::HTML_TEMPLATE)
                    .with_header(header::CONTENT_TYPE, "text/html"))
            } else {
                Ok(Response::from_status(StatusCode::NOT_FOUND)
                    .with_body("Not Found"))
            }
        }
        _ => Ok(Response::from_status(StatusCode::METHOD_NOT_ALLOWED)
            .with_body("Method Not allowed"))
    }
}

fn proxy_to_backend(mut req: Request, backend_name: &str) -> Result<Response, Error> {
    let backend = Backend::from_name(backend_name)?;
    let mut resp = req.send(backend)?;
    
    // Add CORS headers
    resp.set_header("Access-Control-Allow-Origin", "*");
    
    Ok(resp)
}

```

#### Step 4: Deploy option A

```bash
# Build and deploy
cargo build --release --target wasm32-wasip1
fastly compute publish
```

#### Step 5: Test option A

```bash
# Test SDK route
curl https://YOUR_OWN_DOMAIN/sdk/YOUR_API_KEY/loader.js

# Test API route
curl https://YOUR_OWN_DOMAIN/api/events
```

***

### Option B: Use the main domain

This option includes URL transformation to handle `/consent/*` prefixes.

**Prerequisites:** Use the same backends and DNS setup as Option A above.

#### Step 1: Create [fastly.toml](https://github.com/didomi/boilerplate-fastly-reverse-proxy-didomi-cmp/blob/main/fastly.toml) for Option B

```toml
# This file describes a Fastly Compute package. To learn more visit:
# <https://docs.fastly.com/en/guides/compute-configuration>

authors = ["YOUR_EMAIL@YOUR_DOMAIN_NAME"]
description = "Didomi CMP Consent Path Routing"
language = "rust"
manifest_version = 3
name = "boilerplate-fastly-reverse-proxy-didomi-cmp"

[build]
rust_target = "wasm32-wasip1"

[local_server]

  [local_server.backends]

    [local_server.backends.didomi_sdk]
    url = "<https://sdk.privacy-center.org>"

    [local_server.backends.didomi_api]
    url = "<https://api.privacy-center.org>"
```

#### Step 2: Create [Cargo.toml](https://github.com/didomi/boilerplate-fastly-reverse-proxy-didomi-cmp/blob/main/Cargo.toml) for Option B

```toml
[package]
name = "boilerplate-fastly-reverse-proxy-didomi-cmp"
version = "0.1.0"
edition = "2021"

[dependencies]
chrono = "0.4.41"
fastly = "0.11.5"
fern = "0.7.1"
log = "0.4"
log-fastly = "0.11.5"
```

#### 3. Main implementation ([src/main.rs](https://github.com/didomi/boilerplate-fastly-reverse-proxy-didomi-cmp/blob/main/src/main.rs))

```rust
use fastly::http::{header, Method, StatusCode};
use fastly::{Backend, Error, Request, Response};
use log_fastly::Logger;

#[fastly::main]
fn main(req: Request) -> Result<Response, Error> {
    init_logger();
    
    log::info!("Processing request: {} {}", req.get_method(), req.get_path());
    
    route_request(req)
}

/// Routes incoming requests to appropriate Didomi backends.
///
/// Implements two routing strategies:
/// - /api/* routes -> didomi_api backend
/// - /sdk/* routes -> didomi_sdk backend  
/// - All other routes -> 404 Not Found
fn route_request(req: Request) -> Result<Response, Error> {
    let path = req.get_path();
    
    match req.get_method() {
        &Method::GET | &Method::POST | &Method::PUT | &Method::DELETE | &Method::PATCH => {
            if path.starts_with("/api/") {
                // Route API requests to Didomi API backend
                log::info!("Routing API request to didomi_api backend: {}", path);
                proxy_to_backend(req, "didomi_api")
            } else if path.starts_with("/sdk/") {
                // Route SDK requests to Didomi SDK backend
                log::info!("Routing SDK request to didomi_sdk backend: {}", path);
                proxy_to_backend(req, "didomi_sdk")
            } else {
                // Return 404 for unmatched routes
                log::info!("No matching route found for: {}", path);
                Ok(not_found_response())
            }
        }
        _ => {
            // Return 405 Method Not Allowed for unsupported methods
            log::info!("Method not allowed: {}", req.get_method());
            Ok(method_not_allowed_response())
        }
    }
}

/// Proxies a request to the specified backend.
fn proxy_to_backend(mut req: Request, backend_name: &str) -> Result<Response, Error> {
    // Set up backend
    let backend = Backend::from_name(backend_name)?;
    
    // Add CORS headers for preflight requests
    if req.get_method() == &Method::OPTIONS {
        return Ok(cors_preflight_response());
    }
    
    // Forward important headers
    let user_agent = req.get_header_str("User-Agent").unwrap_or("Fastly-Proxy/1.0").to_string();
    req.set_header("Host", get_backend_host(backend_name));
    req.set_header("User-Agent", &user_agent);
    
    // Send request to backend
    let mut resp = req.send(backend)?;
    
    // Add CORS headers to response
    resp.set_header("Access-Control-Allow-Origin", "*");
    resp.set_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
    resp.set_header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
    resp.set_header("Access-Control-Max-Age", "86400");
    
    // Set appropriate cache headers
    match backend_name {
        "didomi_sdk" => {
            // SDK resources can be cached for 1 hour
            resp.set_header("Cache-Control", "public, max-age=3600");
        }
        "didomi_api" => {
            // API responses should not be cached
            resp.set_header("Cache-Control", "no-cache, no-store, must-revalidate");
            resp.set_header("Pragma", "no-cache");
            resp.set_header("Expires", "0");
        }
        _ => {}
    }
    
    log::info!("Successfully proxied request to {} backend", backend_name);
    Ok(resp)
}

/// Returns the appropriate host header for the backend.
fn get_backend_host(backend_name: &str) -> &'static str {
    match backend_name {
        "didomi_sdk" => "sdk.privacy-center.org",
        "didomi_api" => "api.privacy-center.org",
        _ => "unknown"
    }
}

/// Creates a CORS preflight response.
fn cors_preflight_response() -> Response {
    Response::from_status(StatusCode::OK)
        .with_header("Access-Control-Allow-Origin", "*")
        .with_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS")
        .with_header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With")
        .with_header("Access-Control-Max-Age", "86400")
        .with_header("Content-Length", "0")
}

/// Creates a standard 404 Not Found response.
fn not_found_response() -> Response {
    Response::from_status(StatusCode::NOT_FOUND)
        .with_body("Not Found - Only /api/* and /sdk/* routes are supported")
        .with_header(header::CONTENT_TYPE, "text/plain")
        .with_header("Access-Control-Allow-Origin", "*")
}

/// Creates a 405 Method Not Allowed response.
fn method_not_allowed_response() -> Response {
    Response::from_status(StatusCode::METHOD_NOT_ALLOWED)
        .with_body("Method Not Allowed")
        .with_header(header::CONTENT_TYPE, "text/plain")
        .with_header("Allow", "GET, POST, PUT, DELETE, PATCH, OPTIONS")
        .with_header("Access-Control-Allow-Origin", "*")
}

/// Initializes the logger for debugging and monitoring.
fn init_logger() {
    let logger = Logger::builder()
        .default_endpoint("cmp_proxy_log")
        .echo_stdout(true)
        .max_level(log::LevelFilter::Info)
        .build()
        .expect("Failed to build Logger");

    fern::Dispatch::new()
        .format(|out, message, record| {
            out.finish(format_args!(
                "{} [{}] {}",
                chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"),
                record.level(),
                message
            ))
        })
        .chain(Box::new(logger) as Box<dyn log::Log>)
        .apply()
        .expect("Failed to initialize logger");
}
```

### Deployment steps

#### 1. Build the application

```bash
# Build for WebAssembly target
cargo build --target wasm32-wasip1 --release
```

#### 2. Test locally

```bash
# Start local development server
fastly compute serve --service-id YOUR_SERVICE_ID

# Test CMP endpoints (replace YOUR_API_KEY and NOTICE_ID with actual values, e.g., YOUR_API_KEY=24cd3901-9da4-4643-96a3-9b1c573b5264, NOTICE_ID=J3nR2TTU)
curl <http://127.0.0.1:7676/consent/YOUR_API_KEY/loader.js?target_type=notice&target=NOTICE_ID>
```

#### 3. Deploy to Fastly

```bash
# Deploy to production
fastly compute publish

# Verify deployment
curl https://YOUR_DOMAIN_NAME/consent/YOUR_API_KEY/loader.js?target_type=notice&target=NOTICE_ID
```

### Configuration requirements

#### DNS configuration

Point your domain/subdomain to Fastly:

* Create a CNAME record pointing to your Fastly service domain
* Or configure A records to Fastly IP addresses

#### SSL/TLS setup

1. **Upload SSL Certificate** to Fastly (if using custom domain)
2. **Enable TLS** for both backends
3. **Configure SNI** for proper SSL handshake

#### Headers and caching

#### For SDK resources (`/consent/*`):

* **Cache TTL**: 3600 seconds (1 hour)
* **Vary Header**: Accept-Encoding, Accept-Language
* **CORS**: Enabled for cross-origin requests

#### For API endpoints (`/consent/api/*`):

* **Cache TTL**: 0 (no caching)
* **Cache-Control**: no-cache, no-store, must-revalidate
* **CORS**: Enabled with appropriate headers

***

{% hint style="success" %}
After setting up your reverse proxy, update your Didomi SDK snippet to use your own domain instead of `privacy-center.org`. This ensures that the Didomi assets are served from your configured domain.

For more information, see the guide to [serving Didomi assets from your domain](https://developers.didomi.io/cmp/web-sdk/serve-didomi-assets-from-your-domain#configure-the-sdk).
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://developers.didomi.io/api-and-platform/domains/reverse-proxy/implementation-guides/fastly.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
