◆ macOS 27 Golden Gate — Developer Beta

Apache Virtual Hosts
Complete Guide

Run multiple local websites on a single Apache server — each with its own domain name, directory, and SSL certificate — on your MacBook Air M4 running macOS Golden Gate.

🌐
What is a Virtual Host?
Maps a custom domain (myapp.test) to a specific folder on your Mac, just like a production server.
📁
Config location
/etc/apache2/extra/httpd-vhosts.conf — same path as Tahoe and Sequoia.
M4 Compatible
macOS 27 requires Apple Silicon — your M4 is fully supported and optimized.
// Three types of virtual hosts
Name-based
Multiple sites share one IP. Apache reads the Host header to decide which site to serve. Most common for local dev.
myapp.test → /Users/you/Sites/myapp
IP-based
Each site gets its own IP address. Rarely used locally but required when SNI is not available.
192.168.1.10 → site-a
Port-based
Different sites on different ports of the same server. Useful for separating HTTP/HTTPS or multiple apps.
:8080 → site-a, :8081 → site-b
CONCEPT

How Virtual Hosts Work

When your browser requests myapp.test, here is the chain of events that gets it to your project folder:

Browser
myapp.test
/etc/hosts
DNS resolution
127.0.0.1:80
Apache port
VirtualHost block
ServerName match
DocumentRoot
~/Sites/myapp

Apache reads the Host: header from the request, matches it against your ServerName directives in order, and serves files from the matching DocumentRoot. The /etc/hosts file (or dnsmasq) maps your custom domain to 127.0.0.1 so your Mac knows where to send the request.

PATHS

Key File Locations on Golden Gate

These paths are identical on macOS 27 Golden Gate as on macOS 26 Tahoe. The built-in Apache config has not moved.

File / DirectoryPurpose
/etc/apache2/httpd.confMain Apache config — enable modules and includes here
/etc/apache2/extra/httpd-vhosts.confVirtual host definitions — add your sites here
/etc/apache2/extra/httpd-ssl.confSSL/HTTPS virtual host config
/etc/apache2/users/Per-user config files for ~/Sites access
/Library/WebServer/Documents/Default document root (localhost)
~/Sites/Your personal web projects folder
/etc/hostsLocal DNS — maps .test domains to 127.0.0.1
/private/var/log/apache2/error_logApache error log
/private/var/log/apache2/access_logApache access log
STEP 01

Enable Virtual Hosts in httpd.conf

Virtual hosts are disabled by default. You need to uncomment two lines in httpd.conf — one to enable name-based virtual hosts and one to include the vhosts config file.

1
Back up and open httpd.conf
Terminal
# Always back up before editing
sudo cp /etc/apache2/httpd.conf /etc/apache2/httpd.conf.bak
sudo nano /etc/apache2/httpd.conf
2
Uncomment mod_rewrite (needed for most modern sites)
httpd.conf — find and uncomment
# Remove the # from this line:
LoadModule rewrite_module libexec/apache2/mod_rewrite.so
3
Uncomment the vhosts include
httpd.conf — find and uncomment
# Remove the # from this line:
Include /private/etc/apache2/extra/httpd-vhosts.conf
💡

In nano: use Ctrl+W to search for httpd-vhosts. Remove the # at the start of the line. Ctrl+O saves, Ctrl+X exits.

ANATOMY

Understanding a VirtualHost Block

Every virtual host is a <VirtualHost> block inside httpd-vhosts.conf. Here is what each directive does:

httpd-vhosts.conf — annotated
<VirtualHost *:80>
VirtualHost declaration*:80 = match any IP on port 80. Use *:443 for HTTPS.
ServerName myapp.test
ServerNameThe domain this block responds to. Must match your /etc/hosts entry.
ServerAlias www.myapp.test
ServerAliasAdditional names for the same site. Optional.
DocumentRoot "/Users/you/Sites/myapp"
DocumentRootFolder Apache serves files from. Must exist on disk.
ErrorLog "/var/log/apache2/myapp-error.log"
ErrorLogSeparate error log per site. Makes debugging much easier.
CustomLog "/var/log/apache2/myapp-access.log" combined
CustomLogAccess log per site. "combined" format includes IP, time, request, status.
<Directory "/Users/you/Sites/myapp">
Directory blockSets permissions and options for the document root folder.
Options Indexes FollowSymLinks
OptionsIndexes = show file listing if no index.html. FollowSymLinks = allow symlinks.
AllowOverride All
AllowOverride"All" lets .htaccess files override config. Required for WordPress, Laravel, etc.
Require all granted
RequireGrants access to everyone. Use "Require local" to restrict to localhost only.
</Directory>
</VirtualHost>
Closes the VirtualHost block.
STEP 02

Set Up Your First Virtual Host

Open the vhosts config file and add your site. Always keep a default catch-all as the first block — if no ServerName matches the request, Apache falls through to the first VirtualHost defined.

1
Create your project folder
Terminal
# Create the Sites directory if it doesn't exist
mkdir -p ~/Sites/myapp

# Create a quick test page
echo '<h1>myapp.test is working on Golden Gate!</h1>' > ~/Sites/myapp/index.html

# Find your actual username
whoami
2
Edit httpd-vhosts.conf
Terminal
# Back up first
sudo cp /etc/apache2/extra/httpd-vhosts.conf /etc/apache2/extra/httpd-vhosts.conf.bak
sudo nano /etc/apache2/extra/httpd-vhosts.conf
3
Replace the file contents with this config
/etc/apache2/extra/httpd-vhosts.conf
# ── Default catch-all (MUST be first) ────────────────────
<VirtualHost *:80>
    ServerName   localhost
    DocumentRoot "/Library/WebServer/Documents"
    <Directory   "/Library/WebServer/Documents">
        Options       Indexes FollowSymLinks
        AllowOverride All
        Require       all granted
    </Directory>
</VirtualHost>

# ── My First Project ─────────────────────────────────────
<VirtualHost *:80>
    ServerName   myapp.test
    ServerAlias  www.myapp.test
    DocumentRoot "/Users/YOUR_USERNAME/Sites/myapp"
    ErrorLog     "/private/var/log/apache2/myapp-error.log"
    CustomLog    "/private/var/log/apache2/myapp-access.log" combined
    <Directory   "/Users/YOUR_USERNAME/Sites/myapp">
        Options       Indexes FollowSymLinks MultiViews
        AllowOverride All
        Require       all granted
    </Directory>
</VirtualHost>
⚠️

Replace YOUR_USERNAME with your actual macOS username (from whoami). The path must match exactly what's on disk.

4
Add the domain to /etc/hosts
Terminal
sudo nano /etc/hosts

# Add this line at the very bottom of the file:
127.0.0.1    myapp.test
127.0.0.1    www.myapp.test
5
Flush DNS cache and restart Apache
Terminal
# Test config for syntax errors first
sudo apachectl configtest
# Must say "Syntax OK"

# Flush macOS DNS cache
sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder

# Graceful restart
sudo apachectl graceful

# Test it
open http://myapp.test
🎉

You should see your test page at http://myapp.test. Add more projects by repeating steps 1–5 with a new ServerName and DocumentRoot.

STEP 03

Running Multiple Sites

Add as many <VirtualHost> blocks as you need — one per project. Apache matches them top to bottom so the default catch-all must always remain first.

httpd-vhosts.conf — multiple sites example
# ── 1. Default catch-all (always first) ──────────────────
<VirtualHost *:80>
    ServerName   localhost
    DocumentRoot "/Library/WebServer/Documents"
</VirtualHost>

# ── 2. Main app ───────────────────────────────────────────
<VirtualHost *:80>
    ServerName   myapp.test
    DocumentRoot "/Users/YOU/Sites/myapp"
    ErrorLog     "/private/var/log/apache2/myapp-error.log"
    CustomLog    "/private/var/log/apache2/myapp-access.log" combined
    <Directory "/Users/YOU/Sites/myapp">
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

# ── 3. Client site ────────────────────────────────────────
<VirtualHost *:80>
    ServerName   client.test
    DocumentRoot "/Users/YOU/Sites/client"
    ErrorLog     "/private/var/log/apache2/client-error.log"
    CustomLog    "/private/var/log/apache2/client-access.log" combined
    <Directory "/Users/YOU/Sites/client">
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

# ── 4. WordPress site ─────────────────────────────────────
<VirtualHost *:80>
    ServerName   wordpress.test
    DocumentRoot "/Users/YOU/Sites/wordpress"
    ErrorLog     "/private/var/log/apache2/wp-error.log"
    <Directory "/Users/YOU/Sites/wordpress">
        Options       FollowSymLinks
        AllowOverride All
        Require       all granted
    </Directory>
</VirtualHost>

// Add all domains to /etc/hosts in one go

Terminal
sudo nano /etc/hosts

# Add all your local domains at the bottom:
127.0.0.1    myapp.test
127.0.0.1    client.test
127.0.0.1    wordpress.test
STEP 04

HTTPS Virtual Hosts

Add a second <VirtualHost *:443> block for each site that needs HTTPS. First generate a trusted cert with mkcert (see the HTTPS guide), then mirror your HTTP block with SSL directives added.

ℹ️

Generate one mkcert certificate covering all your local domains at once: sudo mkcert myapp.test client.test wordpress.test localhost 127.0.0.1

httpd-vhosts.conf — HTTPS block for one site
# HTTP block (port 80) — redirects to HTTPS
<VirtualHost *:80>
    ServerName  myapp.test
    Redirect    permanent / https://myapp.test/
</VirtualHost>

# HTTPS block (port 443)
<VirtualHost *:443>
    ServerName   myapp.test
    DocumentRoot "/Users/YOU/Sites/myapp"
    ErrorLog     "/private/var/log/apache2/myapp-error.log"
    CustomLog    "/private/var/log/apache2/myapp-access.log" combined

    ## SSL
    SSLEngine             on
    SSLCertificateFile    "/etc/apache2/ssl/myapp.test.pem"
    SSLCertificateKeyFile "/etc/apache2/ssl/myapp.test-key.pem"

    <Directory "/Users/YOU/Sites/myapp">
        Options       Indexes FollowSymLinks
        AllowOverride All
        Require       all granted
    </Directory>
</VirtualHost>

// Generate mkcert certificate for a custom domain

Terminal
# First: install and set up mkcert if you haven't
brew install mkcert nss
mkcert -install

# Generate a cert for your virtual host domain
sudo mkcert -cert-file /etc/apache2/ssl/myapp.test.pem \
            -key-file  /etc/apache2/ssl/myapp.test-key.pem \
            myapp.test www.myapp.test localhost
BONUS

Auto DNS with dnsmasq (No More /etc/hosts Edits)

Instead of adding every new domain to /etc/hosts manually, dnsmasq automatically resolves every *.test domain to 127.0.0.1. Add a project, visit the domain — it just works.

1
Install and configure dnsmasq
Terminal
brew install dnsmasq

# Route all *.test domains to localhost
echo 'address=/.test/127.0.0.1' >> /opt/homebrew/etc/dnsmasq.conf

# Start dnsmasq as a system service
sudo brew services start dnsmasq
2
Tell macOS to use dnsmasq for .test domains
Terminal
sudo mkdir -p /etc/resolver
echo 'nameserver 127.0.0.1' | sudo tee /etc/resolver/test

# Flush DNS to activate
sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder
3
Verify dnsmasq is resolving .test correctly
Terminal
ping -c 1 anything.test
# Should reply from 127.0.0.1 — no /etc/hosts entry needed!

ping -c 1 newproject.test
# Also 127.0.0.1 — any subdomain works automatically

Now when you add a new VirtualHost with ServerName newproject.test, you only need to create the folder and config — DNS resolves automatically. No more /etc/hosts edits needed.

DEBUG

Troubleshooting Virtual Hosts

ProblemCauseFix
Domain loads wrong siteDefault catch-all not first, or ServerName typoCheck block order in vhosts.conf. Default must be first. Check ServerName matches /etc/hosts exactly.
403 ForbiddenDirectory permissions wrongConfirm Require all granted is inside the <Directory> block. Check the folder actually exists.
404 Not FoundDocumentRoot path wrong or file missingRun ls -la /path/to/your/docroot to verify the path and that index.html exists.
Domain doesn't resolveMissing /etc/hosts entry or DNS cache staleAdd 127.0.0.1 yourdomain.test to /etc/hosts. Flush DNS: sudo dscacheutil -flushcache.
"It works!" always showsvhosts include not uncommented in httpd.confCheck Include /private/etc/apache2/extra/httpd-vhosts.conf is not commented out.
Apache won't startSyntax error in configRun sudo apachectl configtest — it will show the exact line number.
.htaccess not workingmod_rewrite not loaded or AllowOverride NoneUncomment mod_rewrite in httpd.conf. Set AllowOverride All in the Directory block.
Changes not reflectedApache not restartedRun sudo apachectl graceful after every config change.

// Essential diagnostic commands

Terminal — diagnose virtual host issues
# Check config syntax
sudo apachectl configtest

# See which vhost Apache would use for a domain
sudo apachectl -t -D DUMP_VHOSTS

# See all active virtual hosts
sudo httpd -t -D DUMP_VHOSTS 2>&1

# Watch error log in real time
tail -f /private/var/log/apache2/error_log

# Watch a site-specific error log
tail -f /private/var/log/apache2/myapp-error.log

# Verify /etc/hosts entry is correct
grep myapp.test /etc/hosts

# Test DNS resolution
ping -c 1 myapp.test

# Graceful restart (picks up all config changes)
sudo apachectl graceful

// View all configured virtual hosts at a glance

Terminal — DUMP_VHOSTS output example
sudo apachectl -t -D DUMP_VHOSTS

# Example output:
VirtualHost configuration:
*:80            localhost (/etc/apache2/extra/httpd-vhosts.conf:3)
*:80            myapp.test (/etc/apache2/extra/httpd-vhosts.conf:14)
*:80            client.test (/etc/apache2/extra/httpd-vhosts.conf:26)
REFERENCE

Quick Command Reference

TaskCommand
Start Apachesudo apachectl start
Stop Apachesudo apachectl stop
Graceful restart (apply config changes)sudo apachectl graceful
Test config for errorssudo apachectl configtest
See all active virtual hostssudo apachectl -t -D DUMP_VHOSTS
Edit main configsudo nano /etc/apache2/httpd.conf
Edit virtual hostssudo nano /etc/apache2/extra/httpd-vhosts.conf
Edit /etc/hostssudo nano /etc/hosts
Flush DNS cachesudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder
Watch error log livetail -f /private/var/log/apache2/error_log
Watch access log livetail -f /private/var/log/apache2/access_log