Skip to content

Hotlink Protection — Prevent Image Leeching

DodaTech 4 min read

Cloudflare Hotlink Protection stops other websites from embedding your images, CSS, and JavaScript files directly — forcing visitors to view your content on your site rather than leeching your bandwidth.

What You Will Learn

You will learn how hotlink protection blocks external referrers, how to configure it for specific file types, and how to allow trusted domains like search engines and social media platforms.

Why It Matters

Hotlinking costs you bandwidth and server resources every time another site displays your images. A single hotlinked image on a popular site can add terabytes of unnecessary transfer costs.

Real-World Use Case

A photography portfolio finds their high-resolution images embedded on dozens of third-party sites without permission, costing $200/month in extra CDN bandwidth. Enabling Hotlink Protection blocks all unauthorised embeds while allowing Pinterest, Google Images, and their own domain.

When a browser requests an image, it sends a Referer header indicating which page the image is displayed on. Hotlink Protection checks this header against your allowed list. Requests from non-allowed referrers receive a 403 response or a replacement image.

flowchart LR
  A[Image Request] --> B{Referer header present?}
  B -->|No / Missing| C[Block]
  B -->|Yes| D{Referer matches allowed?}
  D -->|Yes| E[Serve Image]
  D -->|No| C
  C --> F[403 or Replacement Image]
  E --> G[Visitor Browser]

Enabling via Dashboard

  1. Navigate to Security > Settings.
  2. Find Hotlink Protection and toggle it On.
  3. By default, Cloudflare adds your own domain to the allowlist.
  4. Add additional trusted domains like pinterest.com, google.com, facebook.com.

The feature protects three file types by default: GIF, PNG, JPEG. You can extend it to additional extensions.

curl -X PATCH "https://api.cloudflare.com/client/v4/zones/ZONE_ID/settings/hotlink_protection" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"value": "on"}'

Expected output:

{
  "result": {
    "id": "hotlink_protection",
    "value": "on"
  },
  "success": true
}
import requests

def test_hotlink(zone_id, token):
    url = f"https://api.cloudflare.com/client/v4/zones/{zone_id}/settings/hotlink_protection"
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json",
    }
    resp = requests.get(url, headers=headers)
    data = resp.json()
    status = data["result"]["value"]
    print(f"Hotlink Protection: {status}")
    return status == "on"

# Simulate a hotlink request
image_url = "https://example.com/images/photo.jpg"
hotlink_referer = {"Referer": "https://evil-site.com/page"}
resp = requests.get(image_url, headers=hotlink_referer)
print(f"Hotlink request: {resp.status_code}")

# Simulate a legitimate request
legit_referer = {"Referer": "https://example.com/gallery"}
resp2 = requests.get(image_url, headers=legit_referer)
print(f"Legitimate request: {resp2.status_code}")

Expected output:

Hotlink Protection: on
Hotlink request: 403
Legitimate request: 200

When Cloudflare's default 403 is not enough, serve a replacement image using a Worker:

addEventListener("fetch", (event) => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  const url = new URL(request.url);
  const referer = request.headers.get("Referer") || "";
  const allowed = ["example.com", "www.example.com", "pinterest.com"];

  const isHotlink = url.pathname.match(/\.(jpg|jpeg|png|gif|webp)$/i) &&
    !allowed.some((d) => referer.includes(d));

  if (isHotlink) {
    return new Response(
      "<svg xmlns='http://www.w3.org/2000/svg' width='300' height='200'>" +
      "<rect width='300' height='200' fill='%23eee'/>" +
      "<text x='50%25' y='50%25' fill='%23999' font-size='20' text-anchor='middle'>" +
      "Image protected by Cloudflare</text></svg>",
      {
        status: 200,
        headers: { "Content-Type": "image/svg+xml" },
      }
    );
  }

  return fetch(request);
}

Expected behaviour: Hotlinked images display a placeholder SVG instead of the original.

Common Mistakes

Mistake Consequence
Not allowing Pinterest Loses referral traffic from social pins
Blocking missing Referer headers Some legitimate users (privacy mode) are blocked
Only protecting default extensions WebP, AVIF, and SVG images remain unprotected
Not testing with incognito mode Privacy browsers send empty Referer
Allowing too many domains Defeats the purpose of protection

Practice Questions

  1. What file types does Hotlink Protection cover by default?
  2. Why does blocking requests with no Referer header cause false positives?
  3. How can you serve a replacement image instead of a 403 error?

Challenge

Write a Cloudflare Worker that inspects the Referer header for all image requests and returns a custom watermark SVG for unauthorised referrers while passing through legitimate requests. Deploy and test using curl with different Referer values.

Real-World Task

Your media site has images embedded across 50 unauthorised sites. Enable Hotlink Protection, add Pinterest and Google Images to the allowlist, and verify that your own gallery pages still display images while third-party sites show a 403. Document the bandwidth savings after one week using Cloudflare analytics.

FAQ

Does Hotlink Protection block image requests from search engines?

Google Images sends images with a Referer header from the search results page. If you enable Hotlink Protection without adding google.com to the allowlist, Google Images will show broken placeholders. Add google.com and pinterest.com to preserve search and social traffic.

Can users in private browsing mode still see my images?

Private browsing mode often sends an empty or no Referer header. If Hotlink Protection is configured to block requests without a Referer, these users will see broken images. You can configure the feature to allow empty Referer headers in the Cloudflare dashboard.

Does Hotlink Protection work for CSS and JavaScript files?

By default, Hotlink Protection applies to GIF, PNG, and JPEG only. To protect CSS and JS files, you need to use a WAF custom rule that inspects the Referer header and blocks requests for those extensions from external domains.


Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro — security-first tools for the modern web.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro