Skip to content

Using CloudFlare Workers to Create a Reverse Proxy

Author: Anda Toshiki
Published: 2025-10-04
Updated: Unknown
Words: 1k
Reading: 6 min

Introduction

Cloudflare Workers is a serverless functions platform with global CDN distribution. Free plans include up to 100,000 requests per day. With a simple setup, you can create a reverse proxy to access any website and improve the experience.Cloudflare Workers is a serverless functions platform with global CDN distribution. Free plans include up to 100,000 requests per day. With a simple setup, you can create a reverse proxy to access any website and improve the experience.

Quick Start: Reverse ProxyQuick Start: Reverse Proxy

Below is a Cloudflare Workers reverse proxy script. Replace example.com with your target site’s domain.Below is a Cloudflare Workers reverse proxy script. Replace example.com with your target site’s domain.

Core CodeCore Code

javascript
// Domain of the site to reverse proxy
const upstream = '
example.com
';

// Custom base path (optional)
const upstream_path = '/';

// Upstream domain for mobile devices
const upstream_mobile = '
example.com
';

// Countries/regions to block
const blocked_region = ['CN', 'US'];

// IP addresses to block
const blocked_ip_address = ['0.0.0.0', '127.0.0.1'];

// Force HTTPS
const https = true;

// Disable cache
const disable_cache = false;

// Text replacement rules
const replace_dict = {
  '$upstream': '$custom_domain',
  '//
example.com
': ''
};

// Listen for Fetch events
addEventListener('fetch', event => {
  event.respondWith(fetchAndApply(event.request));
});

// Main logic
async function fetchAndApply(request) {
  const region = request.headers.get('cf-ipcountry')?.toUpperCase();
  const ip_address = request.headers.get('cf-connecting-ip');
  const user_agent = request.headers.get('user-agent');
  let url = new URL(request.url);
  let url_hostname = url.hostname;

  // Set protocol
  url.protocol = https ? 'https:' : 'http:';

  // Choose upstream by device type
  const upstream_domain = await device_status(user_agent) ? upstream : upstream_mobile;

  // Update path and domain

url.host
 = upstream_domain;
  url.pathname = url.pathname === '/' ? upstream_path : upstream_path + url.pathname;

  // Block by region or IP
  if (blocked_region.includes(region)) {
    return new Response('Access denied: this service is not available in your region.', { status: 403 });
  } else if (blocked_ip_address.includes(ip_address)) {
    return new Response('Access denied: your IP has been blocked.', { status: 403 });
  }

  // Set request headers
  const method = request.method;
  const new_request_headers = new Headers(request.headers);
  new_request_headers.set('Host', upstream_domain);
  new_request_headers.set('Referer', `${url.protocol}//${url_hostname}`);

  // Forward the request
  const original_response = await fetch(url.href, { method, headers: new_request_headers });

  // Pass through WebSocket connections
  if (new_request_headers.get('Upgrade')?.toLowerCase() === 'websocket') {
    return original_response;
  }

  const original_response_clone = original_response.clone();
  let response_headers = new Headers(original_response.headers);
  let original_text = null;

  // Set response headers
  if (disable_cache) {
    response_headers.set('Cache-Control', 'no-store');
  }
  response_headers.set('Access-Control-Allow-Origin', '*');
  response_headers.set('Access-Control-Allow-Credentials', 'true');
  ['content-security-policy', 'content-security-policy-report-only', 'clear-site-data'].forEach(header => {
    response_headers.delete(header);
  });

  // Replace text content
  const content_type = response_headers.get('content-type');
  if (content_type?.includes('text/html') && content_type.includes('UTF-8')) {
    original_text = await replace_response_text(original_response_clone, upstream_domain, url_hostname);
  } else {
    original_text = original_response_clone.body;
  }

  // Return the final response
  return new Response(original_text, {
    status: original_response.status,
    headers: response_headers
  });
}

// Replace response text
async function replace_response_text(response, upstream_domain, host_name) {
  let text = await response.text();
  for (const [key, value] of Object.entries(replace_dict)) {
    const search = key === '$upstream' ? upstream_domain : key === '$custom_domain' ? host_name : key;
    const replacement = value === '$upstream' ? upstream_domain : value === '$custom_domain' ? host_name : value;
    text = text.replace(new RegExp(search, 'g'), replacement);
  }
  return text;
}

// Determine device type (mobile or desktop)
async function device_status(user_agent_info) {
  const agents = ['Android', 'iPhone', 'SymbianOS', 'Windows Phone', 'iPad', 'iPod'];
  return !agents.some(agent => user_agent_info.includes(agent));
}// Domain of the site to reverse proxy
const upstream = '
example.com
';

// Custom base path (optional)
const upstream_path = '/';

// Upstream domain for mobile devices
const upstream_mobile = '
example.com
';

// Countries/regions to block
const blocked_region = ['CN', 'US'];

// IP addresses to block
const blocked_ip_address = ['0.0.0.0', '127.0.0.1'];

// Force HTTPS
const https = true;

// Disable cache
const disable_cache = false;

// Text replacement rules
const replace_dict = {
  '$upstream': '$custom_domain',
  '//
example.com
': ''
};

// Listen for Fetch events
addEventListener('fetch', event => {
  event.respondWith(fetchAndApply(event.request));
});

// Main logic
async function fetchAndApply(request) {
  const region = request.headers.get('cf-ipcountry')?.toUpperCase();
  const ip_address = request.headers.get('cf-connecting-ip');
  const user_agent = request.headers.get('user-agent');
  let url = new URL(request.url);
  let url_hostname = url.hostname;

  // Set protocol
  url.protocol = https ? 'https:' : 'http:';

  // Choose upstream by device type
  const upstream_domain = await device_status(user_agent) ? upstream : upstream_mobile;

  // Update path and domain

url.host
 = upstream_domain;
  url.pathname = url.pathname === '/' ? upstream_path : upstream_path + url.pathname;

  // Block by region or IP
  if (blocked_region.includes(region)) {
    return new Response('Access denied: this service is not available in your region.', { status: 403 });
  } else if (blocked_ip_address.includes(ip_address)) {
    return new Response('Access denied: your IP has been blocked.', { status: 403 });
  }

  // Set request headers
  const method = request.method;
  const new_request_headers = new Headers(request.headers);
  new_request_headers.set('Host', upstream_domain);
  new_request_headers.set('Referer', `${url.protocol}//${url_hostname}`);

  // Forward the request
  const original_response = await fetch(url.href, { method, headers: new_request_headers });

  // Pass through WebSocket connections
  if (new_request_headers.get('Upgrade')?.toLowerCase() === 'websocket') {
    return original_response;
  }

  const original_response_clone = original_response.clone();
  let response_headers = new Headers(original_response.headers);
  let original_text = null;

  // Set response headers
  if (disable_cache) {
    response_headers.set('Cache-Control', 'no-store');
  }
  response_headers.set('Access-Control-Allow-Origin', '*');
  response_headers.set('Access-Control-Allow-Credentials', 'true');
  ['content-security-policy', 'content-security-policy-report-only', 'clear-site-data'].forEach(header => {
    response_headers.delete(header);
  });

  // Replace text content
  const content_type = response_headers.get('content-type');
  if (content_type?.includes('text/html') && content_type.includes('UTF-8')) {
    original_text = await replace_response_text(original_response_clone, upstream_domain, url_hostname);
  } else {
    original_text = original_response_clone.body;
  }

  // Return the final response
  return new Response(original_text, {
    status: original_response.status,
    headers: response_headers
  });
}

// Replace response text
async function replace_response_text(response, upstream_domain, host_name) {
  let text = await response.text();
  for (const [key, value] of Object.entries(replace_dict)) {
    const search = key === '$upstream' ? upstream_domain : key === '$custom_domain' ? host_name : key;
    const replacement = value === '$upstream' ? upstream_domain : value === '$custom_domain' ? host_name : value;
    text = text.replace(new RegExp(search, 'g'), replacement);
  }
  return text;
}

// Determine device type (mobile or desktop)
async function device_status(user_agent_info) {
  const agents = ['Android', 'iPhone', 'SymbianOS', 'Windows Phone', 'iPad', 'iPod'];
  return !agents.some(agent => user_agent_info.includes(agent));
}

Steps to Use

  1. Log in to the Cloudflare Workers dashboardLog in to the Cloudflare Workers dashboard
  2. Create a new WorkerCreate a new Worker
  3. Paste the code into the editorPaste the code into the editor
  4. Replace example.com with the target site’s domainReplace example.com with
  5. Save and deploy

Notes

  1. Request limits: Free accounts can make up to 100,000 requests per day
  2. Compliance: Ensure you have legal permission to perform reverse proxying
  3. Performance tuning: Adjust caching and replacement rules to improve speed
  4. Security: Configure region and IP restrictions to prevent abuse
The avatar of contributor named as Anda Toshiki Anda Toshiki

Changelog

Released under the MIT License.