Documentation

1) Identify what’s installed vs used

which -a httpd apachectl
httpd -V
# shows SERVER_CONFIG_FILE and build path
brew --prefix

Read: /usr/sbin/httpd = Apple; /usr/local/opt/httpd/bin/httpd = Homebrew.


2) See what’s actually running (process + ports)

ps aux | egrep "[h]ttpd|[d]nsmasq|[m]ysqld|[m]ariadbd|[p]hp-fpm"
sudo lsof -nP -iTCP:80,443 -sTCP:LISTEN
sudo lsof -nP -iUDP:53
lsof  -nP -iTCP:3306 -sTCP:LISTEN

Read: Path shows which binary owns the port (Apple vs Homebrew). Only one service should own each port.


3) Compare launchd jobs (system vs user)

brew services list
sudo launchctl list | egrep -i "httpd|dnsmasq|mysql|mariadb|php"
launchctl list      | egrep -i "httpd|dnsmasq|mysql|mariadb|php"
launchctl print gui/$(id -u)/homebrew.mxcl.httpd 2>/dev/null | sed -n '1,120p'
sudo launchctl print system/homebrew.mxcl.httpd 2>/dev/null | sed -n '1,120p'

Read:

  • system/ = LaunchDaemon (root), gui/ = LaunchAgent (user).
  • last exit code = 1/2 = failed start (often privileged port or config mismatch).
  • Duplicates (same label in both domains) → conflicts & “Background Items” duplicates.

4) Validate Apache config & vhosts

/usr/local/opt/httpd/bin/httpd -t
/usr/local/opt/httpd/bin/httpd -t -D DUMP_VHOSTS | head -n 50
grep -nE '^[[:space:]]*Listen[[:space:]]+(80|443)\b' /usr/local/etc/httpd/*.conf /usr/local/etc/httpd/extra/*.conf
grep -nE '<VirtualHost[[:space:]]+\*:(80|443)>' /usr/local/etc/httpd/extra/*.conf

Read: If running as user, any Listen 80/443 or <VirtualHost *:80/443> will fail. After switching to 8080, make sure all vhosts match.


5) PHP-FPM wiring (common break)

ls -l /usr/local/var/run/php-fpm.sock 2>/dev/null
lsof -nP -iTCP:9000 -sTCP:LISTEN
# Check httpd.conf or included files for SetHandler proxy:unix:/usr/local/var/run/php-fpm.sock|fcgi://localhost/

6) Permissions on logs/run dirs

ls -ld /usr/local/var/log/httpd /usr/local/var/run/httpd
ls -l  /usr/local/var/log/httpd | head

Read: If owned by root, user-mode httpd can’t open logs → instant exit.


7) DNS sanity (dnsmasq / dev TLDs)

scutil --dns | sed -n '1,150p' | grep -A2 'domain: test' || true
dig @127.0.0.1 proton.test +short
log show --style syslog --predicate 'process == "dnsmasq"' --last 1h

Read: Ensure a resolver for your dev TLD exists; dig should hit 127.0.0.1. VPNs/WARP/Screen Time can override DNS.


8) Error logs (ground truth)

sudo tail -n 200 /usr/local/var/log/httpd/error_log 2>/dev/null
# MySQL/MariaDB:
tail -n 200 /usr/local/var/mysql/*.err 2>/dev/null

Quick interpretations

  • brew services: httpd error 1 (user) → trying to bind :80/443 or log dir perms.
  • Two httpd/dnsmasq entries in Background Items → duplicate LaunchAgent + LaunchDaemon.
  • Nothing on :3306 but DB “running” → port conflict or different port; check *.err.
  • Syntax OK but service dies → ports/permissions/duplicate launch jobs are the usual culprits.

Use the steps in order—by the time you finish #4–#6 you’ll know exactly why it fails and which single change will fix it.

Contents