Behind This Website: Locking Down Ghost Admin with Cloudflare on a Raspberry Pi

by Prasanna Sambasivan

Behind This Website: Locking Down Ghost Admin with Cloudflare on a Raspberry Pi
Photo by FlyD / Unsplash

Share

I run this website off a Raspberry Pi in my LAN. The whole website lives in a Docker container, uses a Cloudflare Tunnel, and stays reachable to the world, but only the parts I want reachable. Why? Here’s the long version.

Short version: I like owning my stuff and wanted to reuse stuff I had rather than pay for something new. No SaaS upsells, no sudden “pricing updates,” no vendor lock-in. Just containers and config files I understand.

Small caveat, this post isn’t a full walkthrough. Just a nudge in the right direction. If you’ve got a Raspberry Pi, a weekend, and some curiosity, you’ll figure it out.


The stack

Here’s what powers my setup:

It’s simple, reliable, and rebuildable in minutes if something breaks.


The old Ghost gotcha

Ghost is great, until you realize the /ghost admin panel is always there. If you self-host it, that login page is open to the entire internet by default, just like /wp-admin on Wordpress.

You can block bots with a robots.txt. You can rename /ghost to something else. But that’s just security by obscurity. I wanted a real lock, which led me to:


Plan A: “Two Access Apps” (the theory)

Cloudflare Access lets you protect parts of your site by identity. So I figured: two “apps.”

Cloudflare matches path rules longest-first. So /ghost* should stay open only for me, while the catch-all blocks the rest.


The reality: the Access rabbit hole

Turns out Cloudflare’s free plan doesn’t support granular path exceptions in policy logic. If your “Allow” is too broad, the catch-all “Block” doesn’t always win. The result? / sometimes shows the Cloudflare login screen even when it shouldn’t.

So I ditched it.


Plan B: the real fix

I moved the admin interface to its own subdomain - let's call it example.arbitraryentity.info.

The Tunnel routes both domains arbitraryentity.info and example.arbitraryentity.info to the same Ghost container.

Result: no visible admin panel to bots or scanners, and only I can get in.


One tiny gotcha: /cdn-cgi/access/

When Cloudflare Access does its login handshake, it hits /cdn-cgi/access/ in the background.

If your WAF rule only allows /ghost*, that handshake fails after login and you hit a 403.

Fix: Add an exception for /cdn-cgi/access/ in your WAF rule.


Why it works


Bonus: backups next

Next up: daily automated backups. I’ll likely cron a job (or use a lightweight container) to export the Ghost content folder and DB dump to an offsite store.

If the Pi dies or I break the theme, I can rebuild it all in minutes.


Lessons learned

And probably the biggest one:

💡
Proper security means you can share the whole playbook you followed to lock things down, without leaking anything that actually matters. The lock is real. Security by obscurity alone isn’t.