Troubleshooting a Sudden Loss of Apache on macOS


            

One morning my local Apache server, installed via Homebrew, simply stopped working. The browser refused to open localhost, even though everything had been fine the day before. What followed was a methodical debugging session that taught me a few things about how Homebrew, launchd, and Apache interact on macOS — and how easy it is to make the problem worse while trying to fix it. Here is the full walkthrough, in case it helps someone else.

Step 1: Check whether Apache is actually running

The first command I always reach for in this situation is:

brew services list

The output showed httpd in an error state, running as root. That immediately told me two things: Apache had tried to start and failed, and it was configured to bind to port 80 (which requires root privileges).

Step 2: Find out who is holding port 80

If brew services reports an error, a common reason is that something else is already occupying the port Apache wants. To check:

sudo lsof -nP -iTCP:80 -sTCP:LISTEN

Surprise — there was something listening on port 80. A single httpd process, running as my user, with a specific PID. So why was brew services reporting an error?

Step 3: Identify the orphan process

ps -p <PID> -o pid,user,command

The output revealed the culprit: /opt/homebrew/bin/httpd -X. The -X flag is debug/foreground mode. I had run that command earlier in my own troubleshooting and it was still alive, holding port 80 hostage. brew services couldn’t start its own managed Apache because the port was taken — hence the error status.

Lesson learned: when you start httpd -X manually, remember to kill it afterward.

Step 4: Clean up and try to restart properly

sudo kill <PID>
sudo lsof -nP -iTCP:80 -sTCP:LISTEN
sudo brew services start httpd

This is where things got worse before they got better. The brew services start command failed with:

Bootstrap failed: 5: Input/output error
Error: Failure while executing; `/bin/launchctl bootstrap system /Library/LaunchDaemons/homebrew.mxcl.httpd.plist` exited with 5.

Bootstrap error 5 from launchctl usually means macOS is rejecting the LaunchDaemon. The likely cause: I now had two competing launch configs on the system — one in /Library/LaunchDaemons/ (system-wide, from running with sudo) and an old one in ~/Library/LaunchAgents/ (user-level, from earlier non-sudo attempts).

Step 5: Full cleanup of launch configs

The important thing to know here is that the plist files are just launch wrappers generated by Homebrew. Deleting them does not touch httpd.conf or any of your sites and vhosts — those live in /opt/homebrew/etc/httpd/ and stay safe.

sudo brew services stop httpd 2>/dev/null
brew services stop httpd 2>/dev/null

sudo launchctl bootout system/homebrew.mxcl.httpd 2>/dev/null
launchctl bootout gui/$(id -u)/homebrew.mxcl.httpd 2>/dev/null

sudo rm -f /Library/LaunchDaemons/homebrew.mxcl.httpd.plist
rm -f ~/Library/LaunchAgents/homebrew.mxcl.httpd.plist

sudo pkill -f httpd
sudo lsof -nP -iTCP:80 -sTCP:LISTEN

Step 6: Reboot

At this point I rebooted the Mac. After the reboot, localhost loaded normally in the browser. Sometimes the cleanest way to clear stuck launchd state is to let the system start fresh.

Step 7: Verify the final state

After reboot, brew services list showed httpd with status none — odd, given that the site was working. Running:

sudo lsof -nP -iTCP:80 -sTCP:LISTEN

showed a perfectly healthy Apache: a parent process owned by root, plus several worker processes owned by my user. That is exactly what a properly running httpd looks like. The parent binds to port 80 (requires root), then spawns workers that drop to a non-privileged user, as configured by the User directive in httpd.conf.

The fact that brew services list still says none is purely cosmetic. Apache is being started by launchd directly via the plist, just not tracked in the brew services registry. It works, it survives reboots, and that is what matters.

What I would do differently next time

The original failure was probably something minor — a transient launchd hiccup, possibly nudged by a recent macOS update. What turned a five-minute fix into an hour of debugging was running sudo httpd -X manually and forgetting about it. That orphan process held port 80 and made every subsequent restart attempt fail in confusing ways.

The single most useful diagnostic command in this whole exercise was:

sudo lsof -nP -iTCP:80 -sTCP:LISTEN

It tells you 90 percent of what you need to know in one line. Nothing listening means Apache is down — start it. Something listening means Apache is up, and the problem is somewhere else: browser cache, DNS, /etc/hosts, or a firewall. Multiple processes from different parents listening on the same port means something is wrong with how Apache was started.

Quick reference

If your Homebrew Apache on macOS suddenly refuses to serve localhost, work through this in order:

  1. Run brew services list and check the status of httpd.
  2. Run sudo lsof -nP -iTCP:80 -sTCP:LISTEN and see whether anything is on port 80.
  3. If something unexpected is on port 80, identify it with ps -p <PID> -o pid,user,command and kill it.
  4. If nothing is listening, start with sudo brew services start httpd.
  5. If that fails with a bootstrap error, clean up the plist files in both /Library/LaunchDaemons/ and ~/Library/LaunchAgents/, then try again.
  6. If all else fails, reboot. Sometimes launchd just needs a fresh start.

And resist the temptation to leave httpd -X running after you finish debugging.

The Complete Amateur Radio SDR Buyer’s Guide and Comparison (2025)

Software Defined Radio (SDR) has transformed the radio hobby. What once required racks of analog hardware can now be done with a small USB dongle, a laptop, and open-source software. But as the market has matured, the choices have multiplied — from sub-$30 dongles to $1,000+ research-grade platforms. This guide covers every major SDR available […]

Comments are closed.