Complete Guide to Migrating Tencent Cloud Static Resources to Cloudflare R2

This article details how to migrate static resources from Tencent Cloud to Cloudflare R2, including using rclone for data transfer, configuring R2 Bucket, and deploying Cloudflare Worker for high-performance static resource hosting.

Feb 1, 2026 · 5 min read · 915 Words · -Views -Comments · Programming
Complete Guide to Migrating Tencent Cloud Static Resources to Cloudflare R2

Due to increasing dissatisfaction with domestic cloud vendors and to improve blog service performance, I’m migrating the blog’s static resources from Tencent Cloud to Cloudflare R2. The steps are quite tedious, including rclone configuration, Worker deployment, and custom domain binding. However, with AI assistance, the entire migration process took about 1 hour, and 0.5GB of static resources were switched over with zero downtime. Here’s a complete record of the steps for everyone’s reference.

Background and Goals

The main purposes of migrating static resources from Tencent Cloud to Cloudflare R2 are:

  1. Cost Reduction: Cloudflare R2 has completely free egress traffic. In the future, I plan to abandon services that rely on domestic clouds, as they are full of rogue practices from login to use.
  2. Performance Improvement: Utilize Cloudflare’s global CDN acceleration to optimize blog access speed and SEO.

Final Architecture

Server local directory
/var/www/blog/static-assets
         (rclone sync)
Cloudflare R2 (private bucket)
         (Worker)
static.1991421.cn (custom domain)

Architecture Features:

  • Blog URLs remain unchanged
  • R2 Bucket remains private
  • Access controlled via Worker
  • Zero egress traffic costs

1. Install rclone

Install rclone on the server:

curl https://rclone.org/install.sh | sudo bash

2. Configure rclone to Connect to R2

1. Start Configuration Wizard

rclone config

2. Create a New Remote

Configure according to the following steps:

  1. Select n to create a new remote
  2. Name: cf-r2
  3. Storage type: s3 (S3 compatible object storage)
  4. Provider: 47 (Any other S3 compatible provider)

Why not select Cloudflare R2 option? R2 is not in the built-in vendor list, so we must use the generic S3 compatible configuration.

3. Fill in Authentication Information

After creating an API token in the Cloudflare R2 console, you will see three fields. Here’s the corresponding relationship:

Cloudflare Console Fieldrclone Config ItemRequired?
Access Key IDaccess_key_id✅ Required
Secret Access Keysecret_access_key✅ Required
Token Value-❌ Ignore

Enter in rclone:

access_key_id>     [Your Access Key ID]
secret_access_key> [Your Secret Access Key]

4. Configure Connection Parameters

Region (leave empty):

region> [Press Enter directly]

R2 doesn’t have a region concept, leaving it empty is the official recommended approach.

Endpoint (required):

endpoint> https://<AccountID>.r2.cloudflarestorage.com

Example: https://xxxxx.r2.cloudflarestorage.com

You can find this address on the Bucket details page in the R2 console.

Location Constraint (leave empty):

location_constraint> [Press Enter directly]

ACL (empty = private):

acl> [Press Enter directly]

R2 doesn’t use S3 ACLs; permissions are managed by Workers.

Advanced config (not needed):

Edit advanced config? n

5. Confirm Configuration

y) Yes this is OK

Configuration complete!

3. Perform Data Migration

rclone copy /var/www/blog/static-assets cf-r2:static-assets \
  --progress \
  --transfers=16 \
  --checkers=16

The copy command only copies files and doesn’t delete files on the destination, making it safer.

Daily Synchronization (Use sync)

rclone sync /var/www/blog/static-assets cf-r2:static-assets \
  --progress \
  --transfers=16 \
  --checkers=16

sync makes the destination exactly match the source and will delete excess files on the destination.

Parameter Explanations:

  • --progress: Show transfer progress
  • --transfers=16: Transfer 16 files concurrently
  • --checkers=16: Check 16 files concurrently

4. Configure Cloudflare R2 Bucket

Create an R2 Bucket in the Cloudflare console:

  • Bucket Name: static-assets
  • Storage Type: Standard
  • Region: Auto
  • Public Access: ❌ Disabled (keep private)

Important: Do not enable public access; we will control access through Workers.

5. Deploy Cloudflare Worker

1. Create a Worker

In the Cloudflare console:

  1. Go to Workers & Pages
  2. Click Create Application
  3. Select Create Worker
  4. Select Start from Hello World

2. Write Worker Code

Paste the following code into the Worker editor:

export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const key = url.pathname.slice(1);

    if (!key) {
      return new Response('Not Found', { status: 404 });
    }

    const object = await env.STATIC_ASSETS.get(key);
    if (!object) {
      return new Response('Not Found', { status: 404 });
    }

    return new Response(object.body, {
      headers: {
        'Content-Type':
          object.httpMetadata?.contentType || 'application/octet-stream',
        'Cache-Control': 'public, max-age=31536000, immutable'
      }
    });
  }
};

Code Explanation:

  • Extract file key from URL path
  • Get object from R2
  • Set correct Content-Type
  • Add long-term cache headers (1 year)

3. Bind R2 Bucket

This is the most critical step and is easily overlooked!

In Worker settings:

  1. Go to Settings > Variables and Secrets
  2. Select R2 Bucket Bindings (not environment variables!)
  3. Add binding:
    • Variable Name: STATIC_ASSETS
    • R2 Bucket: Select static-assets

Note: Must use R2 Bucket binding; do not use environment variables or plain text.

https://static.1991421.cn/2026/02/2026-02-01-201646.jpeg

4. Test Worker

After deployment, test using the workers.dev domain:

curl -I https://static-assets.your-account.workers.dev/2018-06-06-Validate.jpg

If it returns the image correctly, the Worker to R2 connection is established.

6. Bind Custom Domain

1. Add Custom Domain

In Worker settings:

  1. Go to Triggers
  2. Click Add Custom Domain
  3. Enter domain: static.1991421.cn

If the domain is already managed by Cloudflare:

  • DNS records will be created automatically
  • HTTPS will be enabled automatically
  • Usually takes effect within 1 minute

2. Verify Domain

curl -I https://static.1991421.cn/2018-06-06-Validate.jpg

Check response headers:

server: cloudflare
cache-control: public, max-age=31536000, immutable

These indicate that it’s officially online!

7. Update Blog References

If you used a new domain, you need to update the static resource URLs in your blog. In my case, they remain unchanged, so this step isn’t necessary. If you need to update, you can use batch replacement:

Old: https://old-domain.com/image.jpg
New: https://static.1991421.cn/image.jpg

You can use a batch replacement tool to complete this.

Summary

This migration achieved:

Cost Optimization: R2 egress traffic is $0 ✅ Performance Improvement: Cloudflare global CDN acceleration ✅ Architecture Optimization: Standard practice of Private Bucket + Worker ✅ Zero Downtime Migration: URLs remain unchanged, users are unaware

The entire migration process took only 1 hour, and all 0.5GB of static resources were migrated. This is the Cloudflare R2 + Worker approach.

Authors
Developer, digital product enthusiast, tinkerer, sharer, open source lover