Building a Private Jekyll Documentation Site on Cloudflare with Google Login

Goal: Deploy a Jekyll-based documentation site on Cloudflare and restrict access to only approved Google-account users.

If your approved users are managed in GitHub instead of Google, you can keep the same Pages and Access architecture and swap the identity provider to GitHub. The GitHub-specific setup is included below.


Table of contents

  1. What you are building
  2. Recommended architecture
  3. How access control works
  4. Prerequisites
  5. Deployment order that avoids common mistakes
  6. Step 1: Create and test the Jekyll site locally
  7. Step 2: Push the site to GitHub
  8. Step 3: Deploy the site to Cloudflare Pages
  9. Step 4: Attach a custom domain
  10. Step 5: Set up Google or GitHub as an identity provider in Cloudflare Access
  11. Step 6: Create the Access application for your private docs hostname
  12. Step 7: Protect preview deployments and the Pages hostname
  13. Optional hardening
  14. Suggested project files
  15. Validation checklist
  16. Troubleshooting
  17. Operational guidance
  18. Reference links

What you are building

The cleanest way to do this on Cloudflare is:

This means your site stays static and simple, while login enforcement happens at the Cloudflare edge before users can reach the docs.


flowchart LR
    A[Approved user opens docs.example.com] --> B[Cloudflare Access]
    B --> C[Google or GitHub sign-in]
    C --> B
    B -->|Allow policy matched| D[Cloudflare Pages]
    D --> E[Jekyll-built static site]

    X[Unapproved user] --> C
    C --> B
    B -->|Blocked| Y[Access deny page]

Why this architecture is a good fit


How access control works

It helps to separate authentication from authorization:

That distinction matters because your identity provider may allow many users to sign in, but Cloudflare Access decides who may actually see the site.

sequenceDiagram
    participant U as User
    participant A as Cloudflare Access
    participant I as Identity Provider
    participant P as Cloudflare Pages

    U->>A: Request docs.example.com
    A->>I: Redirect to login
    I-->>A: User identity
    A->>A: Evaluate allow / require / exclude rules
    alt authorized
        A->>P: Forward request
        P-->>U: Jekyll docs
    else not authorized
        A-->>U: Access denied
    end

Prerequisites

You should have these before starting:

Use these as a simple layout:

flowchart TD
    A[Domain strategy] --> B[docs.example.com -> production docs]
    A --> C[project.pages.dev -> default Pages hostname]
    A --> D[branch.project.pages.dev -> preview alias]
    A --> E[hash.project.pages.dev -> immutable preview]

Deployment order that avoids common mistakes

Do these in this order:

  1. Build and test Jekyll locally
  2. Push to GitHub
  3. Create the Cloudflare Pages project
  4. Attach the custom domain
  5. Configure Google or GitHub as the Access identity provider
  6. Create a self-hosted Access application for the custom domain
  7. Protect preview deployments and optionally the Pages hostname
  8. Optionally redirect *.pages.dev traffic to the custom domain

Why order matters

Cloudflare documents an important caveat: you cannot add a Pages custom domain if a Cloudflare Access policy is already enabled on that domain. Also, certificate validation can fail if Access or a Worker blocks the ACME challenge path too early.

flowchart TD
    A[Start] --> B[Create Jekyll site]
    B --> C[Deploy to Pages]
    C --> D[Attach custom domain]
    D --> E[Configure IdP]
    E --> F[Create Access app for custom domain]
    F --> G[Protect preview/pages.dev URLs]
    G --> H[Optional redirect pages.dev to custom domain]

Step 1: Create and test the Jekyll site locally

Cloudflare’s Jekyll Pages guide recommends installing a recent Ruby and using Jekyll normally. On macOS, the Jekyll docs recommend not using the system Ruby.

Example bootstrap

rbenv install 3.1.3
rbenv global 3.1.3
gem install bundler jekyll
jekyll new my-private-docs
cd my-private-docs
bundle exec jekyll serve

Then open the local site and confirm it works before you deploy.

Minimal content structure

my-private-docs/
├─ _config.yml
├─ Gemfile
├─ index.md
├─ docs/
│  ├─ getting-started.md
│  └─ architecture.md
└─ assets/

Example index.md

---
layout: home
title: Private Docs
---

# Private Docs

Welcome to the internal documentation portal.
flowchart LR
    A[Markdown files] --> B[Jekyll build]
    B --> C[_site output]
    C --> D[Cloudflare Pages serves static files]

Step 2: Push the site to GitHub

Cloudflare Pages works well with Git integration, so keep the source in GitHub.

git init
git add .
git commit -m "Initial Jekyll docs site"
git branch -M main
git remote add origin https://github.com/<your-user>/<your-repo>.git
git push -u origin main

Good repository habits


Step 3: Deploy the site to Cloudflare Pages

In Cloudflare:

  1. Go to Workers & Pages
  2. Select Create application
  3. Choose Pages
  4. Import the GitHub repository
  5. Use the Jekyll build settings below

Build settings

Setting Value
Production branch main
Build command jekyll build
Build directory _site
Environment variable RUBY_VERSION=3.1.3 (or your local Ruby version)

Notes

sequenceDiagram
    participant Dev as Developer
    participant GH as GitHub
    participant CF as Cloudflare Pages
    participant Site as Production Site

    Dev->>GH: Push commit to main
    GH->>CF: Trigger build
    CF->>CF: Install gems
    CF->>CF: Run jekyll build
    CF->>Site: Publish _site

Step 4: Attach a custom domain

Use a custom domain such as docs.example.com.

Steps

  1. Open your Pages project
  2. Go to Custom domains
  3. Select Set up a domain
  4. Enter docs.example.com
  5. Complete the DNS validation steps

Important caveats

flowchart LR
    A[Cloudflare Pages project] --> B[Attach docs.example.com]
    B --> C[DNS / certificate validation]
    C --> D[Production docs domain is live]

Step 5: Set up Google or GitHub as an identity provider in Cloudflare Access

This is the login layer.

In Google Cloud

  1. Create or choose a Google Cloud project
  2. Go to APIs & Services
  3. Configure the OAuth consent screen
  4. Choose External as the audience type if you want Gmail / general Google accounts to be able to authenticate
  5. Create an OAuth client
  6. Choose Web application

Use these callback values

Then copy the Client ID and Client Secret.

In Cloudflare One

  1. Go to Integrations > Identity providers
  2. Add a new provider
  3. Choose Google
  4. Paste the Client ID and Client Secret
  5. Optionally enable PKCE
  6. Save
  7. Test the connection

Important interpretation

Choosing External in Google lets Google authenticate general Google users.
That does not mean all of them can access your docs. Access policies still decide who gets in.

sequenceDiagram
    participant Admin as Admin
    participant GCP as Google Cloud
    participant CF as Cloudflare One

    Admin->>GCP: Create OAuth consent screen
    Admin->>GCP: Create Web OAuth client
    GCP-->>Admin: Client ID + Client Secret
    Admin->>CF: Add Google identity provider
    CF-->>Admin: Provider ready for Access apps

If you use Google Workspace

Google Workspace integration is useful when you want richer organization-based rules, including group-based access. It is a better fit than generic Google if access is primarily for a company or school domain.

Alternative: use GitHub as the identity provider

If the people allowed to read your docs are already managed in GitHub, Cloudflare Access can use GitHub login instead of Google.

In GitHub

  1. Log in to GitHub
  2. Go to Settings > Developer settings
  3. Select OAuth Apps
  4. Create a New OAuth App
  5. Set Homepage URL to:
    https://<your-team-name>.cloudflareaccess.com
  6. Set Authorization callback URL to:
    https://<your-team-name>.cloudflareaccess.com/cdn-cgi/access/callback
  7. Register the application
  8. Copy the Client ID
  9. Generate and copy the Client Secret

In Cloudflare One

  1. Go to Integrations > Identity providers
  2. Add a new provider
  3. Choose GitHub
  4. Paste the GitHub Client ID and Client Secret
  5. Save
  6. Select Finish setup and authorize the requested GitHub permissions
  7. Test the connection

Important interpretation

GitHub login proves who the user is.
Cloudflare Access policies still decide whether that GitHub user is one of your valid users.

You do not need a GitHub organization to use the GitHub identity provider, but a GitHub organization or team is the cleanest way to maintain an allow-list over time.


Step 6: Create the Access application for your private docs hostname

Now protect docs.example.com.

Access app type

Create a Self-hosted Access application for the custom domain.

Steps

  1. Go to Zero Trust > Access controls > Applications
  2. Select Add an application
  3. Choose Self-hosted
  4. Give it a name such as Private Jekyll Docs
  5. Set session duration
  6. Add public hostname: docs.example.com
  7. Add one or more access policies
  8. Enable the identity provider for this application:
    • Google for the Google flow above
    • GitHub for the GitHub flow above
  9. If only one login method is enabled, you can enable Instant Auth
  10. Save

Option A — specific approved Gmail accounts

Use this when only a handful of people should have access.

Option B — anyone in your Workspace domain

Use this when all company users may access the docs.

Option C — group-based access

Use this when only a specific Workspace group should access the docs.

Option D — approved GitHub organization or team

Use this when your valid-user list is already maintained in GitHub.

This is usually the cleanest GitHub-based policy. Adding someone to the GitHub organization or team becomes the gate for docs access.

If you are not using a GitHub organization, you can still use the exact email allow-list from Option A and let those users authenticate with GitHub instead of Google.

Example authorization patterns

flowchart TD
    A[User authenticated with Google or GitHub] --> B{Policy type}
    B --> C[Exact email match]
    B --> D[Email domain match]
    B --> E[IdP group match]
    B --> J[GitHub org or team match]

    C --> F[Allow alice@gmail.com]
    C --> G[Allow bob@company.com]
    D --> H[Allow @example.com]
    E --> I[Allow docs-readers group]
    J --> K[Allow your-org or docs-readers team]

Start with exact email allow-listing.
It is the safest default for “reserved users only.”

Example policy layout

flowchart TD
    R[Request to docs.example.com] --> G[Google or GitHub login]
    G --> P{Cloudflare Access policy}
    P -->|email, domain, group, or GitHub org/team matched| A[Allow]
    P -->|no matching rule| B[Block]

Step 7: Protect preview deployments and the Pages hostname

This step is easy to miss.

Preview deployments

By default, Pages preview deployment URLs are public. You should protect them if your docs are private.

Enable preview protection

  1. Open your Pages project
  2. Go to Settings > General
  3. Select Enable access policy

This protects preview deployment URLs such as:

Important limitation

That preview setting does not automatically protect:

Those require separate handling.

Protect the main *.pages.dev hostname

Cloudflare documents a known-issues workflow for this:

  1. In your Pages project, go to Settings > Enable access policy
  2. Use Manage on the Access policy created for previews
  3. Edit the public hostname so it protects the exact Pages hostname instead of a wildcard if needed
  4. Re-enable preview protection so you end up with separate policies

If you also use a custom domain

You must create a separate self-hosted Access application for the custom domain.
Otherwise visitors may see an authentication prompt that does not work correctly.

flowchart TD
    A[Private docs project] --> B[Preview URLs]
    A --> C[project.pages.dev]
    A --> D[docs.example.com]

    B --> E[Protect via Pages preview access policy]
    C --> F[Protect via Pages/Access hostname policy]
    D --> G[Protect via separate self-hosted Access app]

Optional: redirect the Pages hostname to the custom domain

Once the custom domain is working, you can use Bulk Redirects so that users land on docs.example.com instead of <project>.pages.dev.

flowchart LR
    A[User opens project.pages.dev] --> B[Bulk Redirect]
    B --> C[docs.example.com]
    C --> D[Cloudflare Access]
    D --> E[Authorized docs]

Optional hardening

1. Add security headers

Cloudflare Pages supports a _headers file for static responses.

Create a file named _headers in your static asset area or build output path.

Example:

/*
  X-Frame-Options: DENY
  X-Content-Type-Options: nosniff
  Referrer-Policy: no-referrer
  Permissions-Policy: document-domain=()
  Content-Security-Policy: default-src 'self'; frame-ancestors 'none';

2. Prevent indexing of Pages hostnames

If you do not want *.pages.dev URLs to appear in search results:

https://:project.pages.dev/*
  X-Robots-Tag: noindex

https://:version.:project.pages.dev/*
  X-Robots-Tag: noindex

3. Use a short Access session duration for sensitive docs

Shorter sessions reduce risk on shared devices.

4. Turn on account-wide default deny carefully

Cloudflare Access has a Require Access protection setting that blocks traffic to any hostname in the account unless an Access application exists for it.

This is powerful, but only enable it after your important hostnames already have proper Access apps.

flowchart TD
    A[Account-wide default deny disabled] --> B[Possible accidental public hostname]
    C[Account-wide default deny enabled] --> D[Only hostnames with Access apps work]

5. Consider Google Workspace integration for larger teams

Use Workspace when you want:


Suggested project files

_config.yml

title: Private Docs
description: Private documentation site
theme: minima
markdown: kramdown
plugins: []

Gemfile

source "https://rubygems.org"

gem "jekyll"
gem "minima"

_headers

/*
  X-Frame-Options: DENY
  X-Content-Type-Options: nosniff
  Referrer-Policy: no-referrer
  Permissions-Policy: document-domain=()

https://:project.pages.dev/*
  X-Robots-Tag: noindex

https://:version.:project.pages.dev/*
  X-Robots-Tag: noindex

index.md

---
layout: home
title: Private Docs
---

# Private Docs

This site is protected by Cloudflare Access and available only to approved users.

Validation checklist

After setup, test all of the following:

Authentication and authorization

Hosting and routing

Security

flowchart TD
    A[Test docs.example.com] --> B{Redirects to correct IdP?}
    B -->|No| C[Check Access app / hostname]
    B -->|Yes| D{Approved user allowed?}
    D -->|No| E[Check Access allow rules]
    D -->|Yes| F{Unapproved user blocked?}
    F -->|No| G[Tighten policy]
    F -->|Yes| H[Setup validated]

Troubleshooting

Problem: Custom domain will not attach

Possible causes:

Problem: Login appears but nobody can get in

Possible causes:

Problem: Preview deployments are still public

Possible causes:

Problem: Custom domain authentication prompt is broken

Possible cause:

Problem: Workspace group rules are unavailable

Possible cause:

Problem: A GitHub organization member is still denied

Possible cause:

Fix:

flowchart TD
    A[Issue found] --> B{What kind of issue?}
    B --> C[Domain attach issue]
    B --> D[Login issue]
    B --> E[Authorization issue]
    B --> F[Preview exposure issue]

    C --> C1[Check Pages custom-domain order]
    C --> C2[Check DNS and certificate validation]
    D --> D1[Check Google or GitHub OAuth client settings]
    D --> D2[Check Cloudflare IdP config]
    E --> E1[Check allow-list emails/domains/groups/orgs]
    F --> F1[Enable preview Access policy]

Operational guidance

Best practice for small private docs

Use this setup:

This is best when you only need a private docs portal for a few approved users.

Best practice for a team or company

Use this setup:

This is easier to maintain as the team changes.

Best practice when valid users are managed in GitHub

Use this setup:

This is a good fit when docs access should follow GitHub membership instead of a separate Google or email allow-list.

flowchart LR
    A[Update docs in GitHub] --> B[Preview deployment]
    B --> C[Review as authorized user]
    C --> D[Merge to main]
    D --> E[Production deploy]
    E --> F[Periodic access-policy review]

Review regularly:


Official documentation used for this guide:

Jekyll

Cloudflare Pages

Cloudflare Access / Zero Trust

Google identity provider

GitHub identity provider


Final recommendation

For your stated requirement — a Jekyll documentation site on Cloudflare that is visible only to reserved users — the best practical setup is:

  1. Deploy the Jekyll site on Cloudflare Pages
  2. Put the production site on a custom domain
  3. Protect that domain with a Self-hosted Cloudflare Access application
  4. Use Google, Google Workspace, or GitHub as the identity provider
  5. Restrict access with an exact email allow-list, domain/group rules, or a GitHub organization/team rule
  6. Protect preview deployments too
  7. Redirect *.pages.dev to the custom domain or secure it separately

If your valid users already live in GitHub, the most maintainable version is usually GitHub IdP + GitHub organization/team policy.