A New Direction: macOS Web Development Environment
Every year for what feels like a decade, I've published a fresh version of the same guide: how to stand up a proper local web development environment on macOS with Homebrew, Apache, and multiple PHP versions. Every new release of macOS broke something — a moved path, a removed script, a deprecated tap — and every year I'd dutifully work through it again, update the screenshots, and post a new three-part series.
This year is different. Instead of writing yet another "edit httpd.conf, run the switcher script, fight with dnsmasq" guide, I sat down and built the tool I always wished existed. It's called Reeve, and this post is both an introduction and a guide to replacing your traditional Homebrew stack with it.
If you want the classic manual approach, the 2024 Sequoia series is still perfectly valid and isn't going anywhere. This post is for those who'd rather not hand-edit Apache configs ever again.
Why I Built Reeve
If you've followed the old guides, you know the drill. You brew install httpd, then open httpd.conf and change the Listen port, the DocumentRoot, the User and Group, the ServerName, uncomment a pile of modules, set up httpd-vhosts.conf by hand, install five or six PHP versions, and then juggle them with a switcher script.
That switcher script — sphp — is one I wrote and maintained myself, and a lot of you have used it over the years. I'm proud of it. But let's be honest about what it actually does: it unlinks one PHP version and links another, then restarts Apache so that mod_php picks up the change. Which means you can only ever run one PHP version at a time. Want to test an old Grav site on PHP 7.4 while you develop a new one on 8.4? You can't — not without switching back and forth and restarting Apache every single time.
That single-version limitation is the wall the whole mod_php + switcher approach hits, and no amount of guide-polishing gets you past it.
Reeve takes a completely different approach. It drops mod_php entirely in favour of PHP-FPM, with one master process per version, all running side by side. Your PHP 7.3 site and your PHP 8.5 site can be open in two browser tabs at the same time, each served by its own FPM master. No linking, no unlinking, no restart-to-switch. You just tell each site which PHP version it wants, and that's that.
Once I had that working, the rest of the stack fell into place around it.
What Reeve Is
Reeve is a localhost web development stack manager for macOS. Think RunCloud or Laravel Herd, but scaled down to your own machine, open source, and built to manage the exact stack we've always recommended in these guides.
It's written in Rust, ships as a single binary, and — this is important — it does not bundle anything. It doesn't ship its own Apache or its own PHP. It orchestrates the same Homebrew-installed binaries you'd install by hand, and manages them as your-user launchd services. No sudo for day-to-day work, no mystery builds, no proprietary config templates. Everything it generates is standard Apache/nginx/Caddy config you could read and understand.
Here's what it does:
- Per-vhost PHP version — each site picks its PHP; versions run simultaneously via FPM.
- Multiple web server backends behind one interface — Caddy, Apache, or nginx (pick your poison, or run several at once on different ports).
- Per-version PHP tuning —
memory_limit, upload sizes, OPcache, the FPM pool, the timezone, and a one-click Xdebug toggle (off/debug/profile), all without hand-editingphp.ini. - Background services — install, start, and stop MySQL, MariaDB, PostgreSQL, Redis, memcached, and Mailpit via Homebrew + launchd, right alongside the web stack.
- Framework presets —
laravel,wordpress,symfony,grav,drupalset the correct docroot and rewrite rules automatically. Or point a vhost at a dev server (Vite, Node…) as a reverse proxy. - Directory parking (Valet-style) — park
~/Sitesand every subfolder automatically serves as<folder>.test, with the framework auto-detected. No per-project setup, ever. - Trusted local SSL via a shared mkcert CA —
https://app.testwith no browser warnings. - Wildcard DNS for one or more TLDs (
.test,.localhost,.lan, …) via a user-run dnsmasq — no root daemon. doctorandlogs— a one-shot health check of the entire stack, and a tail of any service's log.- A TUI dashboard and a scriptable CLI, both driving the same engine underneath.
That bullet list is, more or less, the table of contents of the old three-part series — except now each item is a single command instead of a section of config editing.
Installation
The easiest way to get Reeve is via the Homebrew tap:
brew install yetidevworks/reeve/reeve
If you're a Rust developer, you can install it from crates.io instead (the crate is published as reeve-cli because reeve was already taken, but the installed binary is still reeve):
cargo install reeve-cli
Reeve is built and tested on recent macOS — Tahoe and the Golden Gate beta — on Apple Silicon, and works on Intel Macs too. It needs Homebrew, and if it's missing, Reeve will offer to install it for you.
A Five-Minute Setup
Let's stand up a complete environment from scratch. This is the part that used to be three blog posts.
First, scaffold Reeve's config:
reeve init
Install the PHP versions you care about. Each one gets its own FPM master — and they all run at once:
reeve php install 8.3
reeve php install 8.4
Add a web server. I'll use Caddy here because its config is the simplest, but apache and nginx work identically:
reeve server add caddy
Set up wildcard DNS so every *.test name resolves to your machine. Reeve installs and wires dnsmasq and writes the root-owned /etc/resolver/test file for you via the native macOS admin dialog — that's the one time you'll be asked for your password:
reeve dns setup
Install the local certificate authority so HTTPS "just works" with no warnings:
reeve ssl trust
Now add a site. You can do it one vhost at a time:
reeve vhost add grav.test --root ~/Sites/grav-admin --php 8.3 --server caddy --ssl
…or, my favourite, park your whole ~/Sites folder so every project inside it becomes a working .test site automatically:
reeve park add ~/Sites --server caddy --php 8.3 --ssl
Finally, render the configs and start everything:
reeve apply
reeve server start caddy
That's it. https://grav.test is live, on PHP 8.3, with a trusted certificate. Drop a new folder into ~/Sites, run reeve apply, and it's instantly available too — no per-project command needed.
Migrating from a Traditional Homebrew Setup
If you've followed the old guides, you've already got httpd, a stack of [email protected] kegs, dnsmasq, mariadb, and probably mkcert installed via Homebrew. Good news: Reeve uses all of those. You're not throwing anything away — you're handing the orchestration over to Reeve.
The only thing you need to do first is stop running the old stack yourself, because Reeve wants to manage those processes now. The big one is Apache:
brew services stop httpd
Reeve will actually do this for you. When it goes to start a server or service and finds a conflicting brew services instance of the same software squatting on the port (a leftover brew services start httpd, for example), it stops that job, tells you it did, and takes over. No more "Address already in use" mysteries.
You can also forget about the sphp switcher entirely. Because Reeve runs every PHP version as its own FPM master, which version Homebrew has "linked" no longer matters for serving sites. Link whatever you like for your CLI; your vhosts each get the version you assigned them regardless.
Here's how the old manual steps map onto Reeve:
| The old way (manual) | The Reeve way |
|---|---|
brew install httpd + edit httpd.conf (ports, docroot, user/group, modules) |
reeve server add caddy\|apache\|nginx |
sphp switcher + mod_php, one version at a time |
Per-vhost PHP via FPM — reeve php install, then --php per site |
Hand-edit httpd-vhosts.conf for every site |
reeve vhost add … — or reeve park add ~/Sites |
brew install dnsmasq + /etc/resolver/test by hand |
reeve dns setup |
mkcert -install + per-domain certs + httpd-ssl.conf edits |
reeve ssl trust + --ssl on the vhost |
brew install mariadb + mysql_secure_installation |
reeve service add mariadb && reeve service start mariadb |
pecl install xdebug + php.ini/conf.d juggling |
reeve php xdebug 8.3 debug |
brew services start httpd (with prior sudo dances) |
reeve server start — launchd, no sudo |
Everything you learned in the old guides still applies conceptually — Reeve just stops you from having to type it all out and remember where every config file lives.
The Dashboard
Run reeve with no arguments and you get a live TUI dashboard of the whole stack:

It's fully mouse-driven and scrollable — click a row to select it, scroll through a parked folder with dozens of projects — and every action has a keyboard shortcut. Start and stop servers, flip Xdebug on and off, tune a PHP version's php.ini, add a database, view a log, or run the full health report, all without leaving the dashboard. Anything the CLI can do, the TUI can do too, because they share the same engine.
A Closer Look at the Good Parts
Per-version PHP tuning, no php.ini spelunking
Remember the old dance of finding the right php.ini, editing memory_limit, then doing it again for every version? In Reeve:
reeve php settings 8.3 # show the effective values
reeve php set 8.3 memory_limit 512M # change one
Reeve writes those into the FPM pool it owns, so they survive Homebrew upgrades and never collide with the stock config.
One-click Xdebug
The old guide had an entire section on matching Xdebug versions to PHP versions, editing php.ini, removing the auto-added zend_extension line, and creating a conf.d file. Now:
reeve php xdebug 8.3 debug # or: profile, or: off
It installs Xdebug via pecl on first use and manages the mode for you. In the TUI it's a single keypress. And critically, when it's off, it's genuinely off — Reeve forces the mode as a startup define so a stray ext-xdebug.ini can't quietly leave it instrumenting every request (a subtle, brutal performance drain I chased down while building this).
Services in one command
reeve service add mariadb && reeve service start mariadb
reeve service add mailpit && reeve service start mailpit # SMTP :1025, UI :8025
Reeve adopts each formula's default data directory — no initdb, no data migration. It manages the launchd lifecycle, the logs, and a health check. reeve doctor will even tell you if a service is up but its port isn't answering yet.
Framework presets and reverse proxies
reeve vhost add shop.test --root ~/Sites/shop --php 8.4 --server caddy --ssl --preset laravel
reeve vhost add ui.test --proxy http://localhost:5173 --server caddy --ssl
The laravel preset points the docroot at public/ and adds the right rewrites; grav emits the same access-control rules Grav's .htaccess provides on Apache (now correctly translated for nginx and Caddy too); the --proxy form turns a vhost into a reverse proxy for your Vite or Node dev server.
Directory parking — the part I use most
This is the feature that changed how I work day to day, and it's worth its own spotlight. Instead of registering a vhost for every project, you park a directory once:
reeve park add ~/Sites --server caddy --php 8.3 --ssl
From that moment, every folder inside ~/Sites is automatically served as <folder>.test. Drop in blog, and https://blog.test works. Clone a client project into acme-shop, and https://acme-shop.test is live. There's no per-project command — new folders are picked up on the next reeve apply, and Reeve mints a trusted certificate for each one as it appears.
It's even smart about what it finds. Each parked folder is framework-detected using the same presets above — a Laravel app gets its public/ docroot, a Grav site gets the right security rules — so the convenience doesn't cost you correctness. Folders with awkward names are quietly slugified into valid hostnames (a folder literally called Client Site 2 becomes client-site-2.test), and the TUI gives parked sites their own scrollable panel so you can eyeball dozens of them at a glance.
If you've ever used Laravel Valet, this will feel familiar — except it works across Caddy, Apache, and nginx, with per-folder PHP versions, and it isn't tied to a single framework.
You can park more than one directory, and point each at a different TLD — say ~/Sites on .test and ~/work on .localhost — so different bodies of work stay neatly separated.
Health and logs
reeve doctor # Homebrew, servers, FPM, services, DNS, certs, ports
reeve logs server-caddy # tail any service's log
doctor gives you an honest, port-aware status for everything: a server only reads as "running" when its port is genuinely bound, otherwise you get "loaded, not bound", ":port held by
A Note on Performance
This one matters for Grav users especially. While building Reeve I found that the Grav admin dashboard — which fires a lot of small API calls — was loading noticeably slower under FPM than under the old mod_php setup. The culprit turned out to be the launchd ProcessType Reeve was assigning its services: Background, which on Apple Silicon throttles the process onto the efficiency cores at low priority.
Switching the services to Interactive QoS took a Grav admin dashboard load from ~5.5 seconds back down to ~0.5 seconds. If you've ever felt your local Grav admin was sluggish, this is the kind of thing Reeve now gets right by default.
Honest Limitations
I want to be straight about what Reeve doesn't do, because the old Part 3 covered some of it:
- It's macOS-only, and built around Homebrew + launchd. That's by design.
- It does local, trusted SSL via mkcert. The public Let's Encrypt + Certbot + port-forwarding workflow from Part 3 is out of scope — for exposing a local site to the internet I'd reach for a tunnel (Cloudflare Tunnel, ngrok) rather than poking holes in your router these days.
- OpenLiteSpeed is wired into the backend abstraction but not usable on macOS today (no working build), so stick to Caddy, Apache, or nginx.
Wrapping Up
For years the answer to "how do I set up a good dev environment on my Mac?" was a long, careful, slightly fragile sequence of brew commands and config edits. Reeve is my attempt to turn all of that institutional knowledge into a single tool that does it correctly, repeatably, and with PHP versions that finally run side by side instead of one-at-a-time.
It's open source, it's free, and it's on GitHub. If you've been running the manual stack from the old guides, give the migration a try — I think you'll find your ~/Sites folder lights up with working .test sites faster than you can finish your coffee. ☕
And of course, the very best way to test your shiny new setup is to download Grav and drop it into your parked sites folder. 😆
Questions, problems, or want to follow along as Reeve evolves? Hop into the #macos channel on our Discord — I'm there, and I'd love your feedback.