Skip to content
Cloudflare Docs

Serve private images

You can serve private images by using signed URL tokens. When an image requires a signed URL, the image cannot be accessed without a token unless it is being requested for a variant set to always allow public access.

  1. In the Cloudflare dashboard, go to the Hosted Images page.

    Go to Hosted images
  2. Select Keys.

  3. Copy your key and use it to generate an expiring tokenized URL.

Generate signed URLs from your backend

Signed URLs are generated server-side to protect your signing key. The example below uses a Cloudflare Worker, but the same signing logic can be implemented in any backend environment (Node.js, Python, PHP, Go, etc.).

The Worker accepts a regular Images URL and returns a signed URL that expires after one day. Adjust the EXPIRATION value to set a different expiry period.

JavaScript
const EXPIRATION = 60 * 60 * 24; // 1 day
const bufferToHex = (buffer) =>
[...new Uint8Array(buffer)]
.map((x) => x.toString(16).padStart(2, "0"))
.join("");
async function generateSignedUrl(url, signingKey) {
// `url` is a full imagedelivery.net URL
// e.g. https://imagedelivery.net/cheeW4oKsx5ljh8e8BoL2A/bc27a117-9509-446b-8c69-c81bfeac0a01/mobile
const encoder = new TextEncoder();
const secretKeyData = encoder.encode(signingKey);
const key = await crypto.subtle.importKey(
"raw",
secretKeyData,
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"],
);
// Attach the expiration value to the URL
const expiry = Math.floor(Date.now() / 1000) + EXPIRATION;
url.searchParams.set("exp", expiry);
// `url` now looks like
// https://imagedelivery.net/cheeW4oKsx5ljh8e8BoL2A/bc27a117-9509-446b-8c69-c81bfeac0a01/mobile?exp=1631289275
const stringToSign = url.pathname + "?" + url.searchParams.toString();
// e.g. /cheeW4oKsx5ljh8e8BoL2A/bc27a117-9509-446b-8c69-c81bfeac0a01/mobile?exp=1631289275
// Generate the HMAC signature
const mac = await crypto.subtle.sign(
"HMAC",
key,
encoder.encode(stringToSign),
);
const sig = bufferToHex(new Uint8Array(mac).buffer);
// Attach the signature to the URL
url.searchParams.set("sig", sig);
return new Response(url);
}
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
const imageDeliveryURL = new URL(
url.pathname
.slice(1)
.replace("https:/imagedelivery.net", "https://imagedelivery.net"),
);
// IMAGES_SIGNING_KEY is set via `npx wrangler secret put IMAGES_SIGNING_KEY`
return generateSignedUrl(imageDeliveryURL, env.IMAGES_SIGNING_KEY);
},
};