Cloudflare Security Pages

Blocking Access to Deployment Previews and app.pages.dev Domains in Cloudflare Pages

After creating my first Cloudflare Pages application, I wanted to ensure that access to the app.pages.dev URL and all deployment preview URLs was restricted. Unfortunately, these options don't currently exist on the Pages applications themselves so a few workarounds are necessary.

Update February 18, 2025: Block Everything Without Using Cloudflare Access

After a little playing around and thinking it's still not ideal to have the Cloudflare Access login page pop up for all those development preview URLs, I made some modifications to the functions/_middleware.js to just block everything.

Instead of checking the full hostname for just the primary pages.dev URL, update it to split the hostname and check the last two parts for "pages" and "dev". This also has the added benefit of being easily reusable across multiple projects without having to update the application name each time.

export async function onRequest(context) {
    try {
        const { request } = context;
        const { headers } = request;
        const hostname = headers.get("host");
        const parts = hostname.split(".");
        const tld = parts.pop();
        const domain = parts.pop()

        if (domain === "pages" && tld === "dev") {
            return new Response("I'm a teapot", { status: 418 });
        }

        return await context.next();
    } catch (err) {
        return new Response(`${err.message}\n${err.stack}`, { status: 500 });
    }
}

Original Post:

Blocking Access to Deployment Preview URLs

The process for blocking access to all of the *.app.pages.dev deployment preview URLs is fairly straightforward.

On your application's Settings page under the General heading is an "Access Policy" option. Click the "enable" link for this option, and it will update to say "Manage" which will open a new tab/window for Cloudflare Access. You'll be greeted with a screen for adding your application with some default settings. For the most part you can keep these as-is, specifically you'll need to keep the asterisk (*) in the box under Subdomain. With the asterisk in place for Subdomain, this will only apply to the development preview URLs which is what we want for now.

It will also create a default policy for use in this application that allows access to only your email address. If your intention is to block everyone, change the Action to "Block" and under the existing Include rule change the Selector to "Everyone". If you do need access for specific users feel free to update the policy rules to what works for you.

I created my own reusable policy and removed the legacy policy for this, but it works the same either way.

Blocking Access to the Primary app.pages.dev URL

This one was a little trickier. Cloudflare wants you to Enable Access on your *.pages.dev domain by deleting the wildcard in the Subdomain field on your Access application's settings, and then add a separate application to allow access to your custom domain. Unfortunately, I could not get this to work without also prompting everyone to enter their email address and that's just not going to work. There's quite a few Cloudflare Community pages inquiring about this, and one response in particular had a solution that was quick and easy.

In your Pages application, add a functions/_middleware.js file (or modify your existing if you already have one) to return a custom Response() if the hostname matches your app.pages.dev URL. Alternatively, if you don't want to redirect your app.pages.dev domain to your custom domain, you could also return a 403 instead. Both options are shown below.

export async function onRequest(context) {
    try {
        const { request } = context;
        const { headers } = request;
        const hostname = headers.get("host");

        if (hostname === "app.pages.dev") {
            const redirectUrl = "https://customdomain.com";
            return new Response(null, { status: 301, headers: { "Location": redirectUrl } });
        }

        return await context.next();
    } catch (err) {
        return new Response(`${err.message}\n${err.stack}`, { status: 500 });
    }
}
export async function onRequest(context) {
    try {
        const { request } = context;
        const { headers } = request;
        const hostname = headers.get("host");

        if (hostname === "app.pages.dev") {
            return new Response('Access Denied', { status: 403 });
        }

        return await context.next();
    } catch (err) {
        return new Response(`${err.message}\n${err.stack}`, { status: 500 });
    }
}

You May Also Like

Send Email Through Mailgun From Cloudflare Pages Function
Mailgun Cloudflare Pages

Send Email Through Mailgun From Cloudflare Pages Function

After a lot of troubleshooting various packages and attempting to follow multiple pieces of official documentation that would not work, I ended up at a simple way to send email through the Mailgun HTTP API from a Cloudflare Pages function.

Read Article
Configuring a Cloudflare R2 Bucket and Worker For Public Access
Cloudflare R2 Workers

Configuring a Cloudflare R2 Bucket and Worker for Public Access

I recently published a plugin to integrate Cloudflare R2 with Craft CMS. After having a chance to utilize this on a few more projects after its release, I wanted to put together a guide to streamline this process for future use cases. Hopefully this will help out any others out there looking to setup R2 on their Cloudflare accounts, as most of this won't be specific to Craft CMS.

Read Article
Securing a Site With a Cloudflare Client Certificate and mTLS
Cloudflare Security

Securing a Site With a Cloudflare Client Certificate and mTLS

When a website required limited access, I needed a way to lock it down to specific physical devices. I couldn't rely on IP addresses which might change regularly, and while a strong password requirement might be sufficient I wanted something a little more secure. Not to mention that it shouldn't be crawled by any search engines either. The solution was a Cloudflare client certificate and mTLS firewall rule.

Read Article