From VPS to Raspberry Pi: Self-Hosting WordPress at Home
— 2025.12.25For years, I ran my WordPress sites on a VPS. It just worked. Yearly bill, someone else’s hardware, the usual deal. But CentOS 7 reached end of life, which meant rebuilding everything from scratch anyway. If I had to start fresh, why not do it on my own hardware? Something about having a tiny computer in my living room serving my websites to the world felt… right. So I made the jump.
Why bother?
The honest answer? I wanted to. There’s something satisfying about owning the entire stack, from the hardware sitting on my shelf to the websites people visit. Plus, the Pi 5 is surprisingly capable, and my static IP made the whole thing feasible without too many networking headaches.
The practical answer: cost savings over time, full control over my environment, and a platform I can expand however I want. I’m already running WireGuard, AdGuard, Home Assistant, and a media stack alongside WordPress. One box, many jobs.
The setup
My infrastructure looks like this:
- Raspberry Pi 5 running Docker
- Caddy as a reverse proxy (handling SSL termination)
- Cloudflare for DNS and their proxy/CDN
- Let’s Encrypt certificates, automatically managed by Caddy
- Port forwarding on my router: 80, 443, and 51820 (WireGuard)
Each WordPress site runs in its own container. Caddy sits in front of everything, routing requests to the right container based on the domain.
The Caddyfile is refreshingly simple:
# Global options
{
email me@example.com
log {
output file /data/logs/access.log {
roll_size 10mb
roll_keep 5
roll_keep_for 48h
}
}
}
example.com, www.example.com {
reverse_proxy example.com:80
log {
output file /data/logs/example.access.log {
roll_size 10mb
roll_keep 5
roll_keep_for 48h
}
}
}
No http:// prefix - that’s intentional. Without it, Caddy automatically obtains Let’s Encrypt certificates and serves everything over HTTPS. Add another site? Add another block. Done.
The redirect loop problem
This is where things got interesting. After pointing my Cloudflare DNS to my home IP (with the orange cloud proxy enabled and SSL set to “Full strict”), my site loaded… and immediately crashed into an infinite redirect loop.
The issue came down to architecture. On my old VPS, the flow was straightforward:
Cloudflare → Apache (SSL + WordPress)
WordPress received HTTPS requests directly and knew the connection was secure.
On the Pi, it’s different:
Cloudflare → Caddy (SSL termination) → WordPress container (HTTP on port 80)
Caddy handles the SSL handshake and forwards requests to WordPress over plain HTTP internally. WordPress sees an HTTP request, decides it should be HTTPS, redirects - and the loop begins.
The fix lives in wp-config.php:
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
$_SERVER['HTTPS'] = 'on';
}
Caddy sends headers telling WordPress the original request was HTTPS. This snippet makes WordPress listen. Loop solved.
Adding more sites
For sites that don’t use Cloudflare’s proxy, Caddy handles Let’s Encrypt directly. Point the DNS A record to your Pi’s public IP, add the domain to your Caddyfile, and Caddy does the rest. No extra configuration - just working HTTPS.
For Cloudflare-proxied sites, remember to set SSL mode to “Full (strict)” so Cloudflare validates your origin certificates properly.
What’s next
This post covers the web server foundation. In future posts, I’ll write about the other services running on this little machine - WireGuard for secure remote access, AdGuard for network-wide ad blocking, and the media stack that keeps everything organized.
The Pi 5 handles all of it without breaking a sweat. For a WordPress developer used to managing remote servers, having everything local and tangible is a different kind of satisfying.
If you’re considering the jump from VPS to self-hosting, the learning curve isn’t steep - it’s just different. And once it clicks, you’ll wonder why you didn’t do it sooner.