Context: the live site's PHP stopped executing at some point (matthewmorrone.com now
serves .php files as raw static text instead of running them — confirmed by fetching
ipa.php directly and getting back unexecuted PHP source with
content-type: application/x-httpd-php, plus a 405 on the POST requests the chart's
AJAX calls make). Root cause: the site's other repo (matthewmorrone.github.io) has a
wrangler.jsonc and deploys as static Cloudflare Pages/Workers assets — a static-only
host, so PHP was never actually running. This doc is the plan (now largely executed)
for moving hosting to a Raspberry Pi on the LAN, exposed via a Cloudflare Tunnel, so
PHP actually runs.
Pi identity: the target Pi is portapi (SSH alias pi, LAN IP 192.168.0.215,
Debian 13 "trixie", also reachable over Tailscale). It is not a dedicated box — it
already runs Pi-hole (owns port 80), Plex (32400), Jellyfin (8096), qBittorrent
(8080/8081), Samba, an MQTT broker, and Docker containers for smart-home integrations.
Nothing about this setup should touch those.
Status: done —
- Apache 2.4 + PHP 8.4 installed,
short_open_tag = Onset (/etc/php/8.4/apache2/conf.d/99-short-open-tag.ini). - Apache listens on port 8090 (not 80 — Pi-hole's admin UI owns port 80; changed
in
/etc/apache2/ports.confand the vhost). - DocumentRoot is
/var/www/matthewmorrone.com(not/var/www/html— that's Pi-hole's own doc root, sharing anadmin/folder; kept fully separate to avoid any risk of anrsync --deleteever touching Pi-hole's files). mod_rewriteandmod_headersenabled,AllowOverride Allset so the site's.htaccess(CORS headers,ipa-chart/→ipa/rewrite, etc.) works.matthewmorrone.github.io(main site) rsynced to/var/www/matthewmorrone.com/.- This
iparepo rsynced to/var/www/matthewmorrone.com/projects/ipa/. - Verified over LAN:
ipa.phpmode=1 returns the real language-file JSON list, mode=2 returns the full rendered chart table (~26KB HTML) — PHP is executing correctly.
Also deployed: the other project repos build.sh normally pulls in —
diachron-db, flash, morse, sheet, chess, spaaace (as space) — were
cloned directly on the Pi into /var/www/matthewmorrone.com/projects/ and verified
(HTTP 200 each over LAN).
Cloudflare Tunnel: created and routed to the test hostname.
- Tunnel
pi-websitecreated in the Zero Trust dashboard (account was on no Zero Trust plan yet — selected Free). cloudflaredinstalled on the Pi via Cloudflare's apt repo, running as a systemd service (sudo cloudflared service install <token>), connector shows Connected.- Public hostname
pi-test.matthewmorrone.com→ HTTP →localhost:8090added (the de-risk step from "4. De-risk the cutover" below) — not the root domain yet. - Verified live:
https://pi-test.matthewmorrone.com/returns 200, andipa.phpthrough the tunnel returns correct data (mode=1 language list). Full path confirmed working: public HTTPS → Cloudflare Tunnel → Pi Apache:8090 → PHP.
Cutover: done. matthewmorrone.com and www.matthewmorrone.com now route through
the tunnel to the Pi:
- Both DNS records changed from CNAME →
matthewmorrone-github-io.pages.dev(Cloudflare Pages) to CNAME →35781c67-9f6a-4c42-8e52-1e22c3ca8e9d.cfargotunnel.com. - Tunnel ingress rules added for both hostnames →
http://localhost:8090(in addition to the existingpi-testrule). - Verified live:
https://matthewmorrone.com/→ 200;wwwredirects to the apex (pre-existing redirect, unrelated to this change) which then serves 200;ipa.phpmode=1 and mode=2 both return correct data through the public domain. - Note: the tunnel's "Add route" UI couldn't auto-create the DNS records itself
(it errored with "DNS record already exists" against the pre-existing Pages CNAMEs)
— the DNS records were edited manually instead, pointed at
<tunnel-id>.cfargotunnel.com, before adding the ingress rules separately.
Gap still open:
autocorrect(linked fromleft.html) has no matching repo undergithub.com/matthewmorrone(autocorrect,autocorrect-web,autocorrects,autocorrect.jsall 404) and isn't referenced inbuild.sheither — its source needs to be tracked down before that nav link will work.
Redeploying after local changes is just:
rsync -az --delete --exclude='.git' --exclude='.DS_Store' \
/Users/matthewmorrone/Projects/matthewmorrone.github.io/ pi:/var/www/matthewmorrone.com/
rsync -az --delete --exclude='.git' --exclude='.DS_Store' \
/Users/matthewmorrone/Projects/ipa/ pi:/var/www/matthewmorrone.com/projects/ipa/No MySQL is required — despite the original README, ipa.php never touches a
database; it only reads files from languages/ and tables/.
- Zero Trust dashboard → Networks → Tunnels → Create a tunnel
- Choose Cloudflared, name it (e.g.
pi-website) - Pick the Debian/ARM install command it generates — a one-liner with a token baked in, e.g.:
sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared $(lsb_release -cs) main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt update && sudo apt install cloudflared
sudo cloudflared service install <TOKEN-FROM-DASHBOARD>This installs cloudflared as a systemd service that auto-connects and
auto-restarts on reboot.
In the dashboard, on that tunnel, add Public Hostnames:
matthewmorrone.com→ ServiceHTTP→localhost:8090www.matthewmorrone.com→ same
Adding a public hostname here automatically creates/overwrites the CNAME DNS record for that hostname, pointing it at the tunnel. This is the step that actually cuts the live domain over from its current origin to the Pi.
Before adding the root matthewmorrone.com hostname, add a throwaway one first
(e.g. pi-test.matthewmorrone.com → localhost:8090) and verify the whole site —
including the IPA chart's AJAX calls to ipa.php — loads correctly through that
while the real domain is untouched. Only once that checks out, add the root and
www hostnames to go live. If anything looks wrong after cutover, edit/delete the
public hostname in the dashboard to revert instantly — it's a DNS change, not
destructive to anything on the Pi.
- Deploy the other project repos (
diachron-db,flash,morse,sheet,chess,spaaace, andautocorrect) to/var/www/matthewmorrone.com/projects/the same wayipawas, so those nav links stop 404ing. - Create the Cloudflare Tunnel and point it at
localhost:8090(steps 2–4 above, requires the Cloudflare Zero Trust dashboard — not done yet).