macOS 27 Golden Gate · Developer Beta · MacBook Air M4

Host a Static HTML Site
on Built-in Apache

Everything you need to serve a clean HTML and CSS website from your Mac — no JavaScript, no PHP, no frameworks. Just markup and style, served by Apache.

HTML files CSS stylesheets Images Multiple pages Perl (later) JavaScript PHP
http://mysite.test
WHY

Why Apache for a Static HTML Site?

You could open an HTML file directly in a browser (file:///), but using Apache gives you a real server environment that works just like production hosting. Links, paths, and CSS references all behave correctly, and you can access the site from any device on your local network — phone, tablet, another computer — using your Mac's IP address.

file:// (double-click)

Works for single pages. Relative paths can break. No network access from other devices. URLs look like file:///Users/you/...

http:// via Apache ✓

Behaves exactly like a real server. Clean URLs. Access from phone and tablet on your WiFi. CSS and image paths always resolve correctly.

💡

Apache is already installed on your Mac — no downloads needed. For a pure HTML/CSS site, the configuration is minimal and nothing can break at runtime since there's no code executing. Apache simply reads the file and sends it to the browser.

STRUCTURE

Plan Your Site Folder Structure

Before touching Apache, decide where your files will live and how to organize them. Here is a clean structure for a multi-page HTML/CSS site:

Recommended folder layout
~/Sites/mysite/ ← your project root ├── index.html ← homepage (required) ├── about.html ├── contact.html ├── css/ │ ├── style.css ← main stylesheet │ ├── layout.css │ └── print.css ← print media query stylesheet ├── images/ │ ├── logo.png │ ├── hero.jpg │ └── favicon.ico ├── fonts/ ← self-hosted fonts (optional) │ └── myfont.woff2 └── .htaccess ← Apache overrides (optional)

// Create the structure in Terminal

Terminal
# Create the project directory tree
mkdir -p ~/Sites/mysite/css
mkdir -p ~/Sites/mysite/images
mkdir -p ~/Sites/mysite/fonts

# Confirm your username for config files
whoami

# Check the full path
echo ~/Sites/mysite
# Prints: /Users/YOUR_USERNAME/Sites/mysite
APACHE

Configure Apache for Your Site

Three files to edit, in order. All require sudo for the Apache config files. Replace YOUR_USERNAME with your actual macOS username everywhere.

1
Enable Virtual Hosts in httpd.conf

Open the main Apache config and uncomment the vhosts include line.

Terminal
sudo cp /etc/apache2/httpd.conf /etc/apache2/httpd.conf.bak
sudo nano /etc/apache2/httpd.conf

Use Ctrl+W to search for httpd-vhosts. Remove the # from this line:

httpd.conf — uncomment this line
Include /private/etc/apache2/extra/httpd-vhosts.conf

Also search for mod_rewrite and uncomment (useful for clean URLs later):

httpd.conf — also uncomment
LoadModule rewrite_module libexec/apache2/mod_rewrite.so
💡

Ctrl+O saves in nano, Ctrl+X exits. Search with Ctrl+W.

2
Create the Virtual Host definition

Open the vhosts config file and add your site block. The default catch-all must always come first.

Terminal
sudo nano /etc/apache2/extra/httpd-vhosts.conf

Replace the entire file contents with this — change YOUR_USERNAME on each line:

/etc/apache2/extra/httpd-vhosts.conf — paste this in full
# ── Default (always keep first) ────────────────────────
<VirtualHost *:80>
    ServerName   localhost
    DocumentRoot "/Library/WebServer/Documents"
    <Directory "/Library/WebServer/Documents">
        Options       Indexes FollowSymLinks
        AllowOverride None
        Require       all granted
    </Directory>
</VirtualHost>

# ── My HTML/CSS Site ────────────────────────────────────
<VirtualHost *:80>
    ServerName   mysite.test
    ServerAlias  www.mysite.test
    DocumentRoot "/Users/YOUR_USERNAME/Sites/mysite"

    ErrorLog     "/private/var/log/apache2/mysite-error.log"
    CustomLog    "/private/var/log/apache2/mysite-access.log" combined

    <Directory "/Users/YOUR_USERNAME/Sites/mysite">
        # Show directory listing if no index.html
        Options       Indexes FollowSymLinks

        # Allow .htaccess files to override settings
        AllowOverride All

        # Grant access to everyone (localhost only in practice)
        Require       all granted
    </Directory>

    # Welcome page priority — tries index.html first
    DirectoryIndex index.html index.htm
</VirtualHost>
3
Map the domain in /etc/hosts
Terminal
sudo nano /etc/hosts

# Add these two lines at the very bottom:
127.0.0.1    mysite.test
127.0.0.1    www.mysite.test
4
Test config, flush DNS, restart Apache
Terminal — run in order
# Check for typos in config
sudo apachectl configtest
# Must print "Syntax OK"

# Flush DNS cache (Golden Gate / Tahoe)
sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder

# Apply config changes gracefully
sudo apachectl graceful

# Start if not already running
sudo apachectl start

Apache is now configured. Next step is to put your HTML files in ~/Sites/mysite/ and open http://mysite.test in your browser.

HTML

Write Your First HTML Page

Create a real, working index.html and css/style.css to prove everything is connected. These are clean, minimal starting-point files you can build from.

// Create the homepage

HTML~/Sites/mysite/index.html
<!-- Save this as ~/Sites/mysite/index.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>My Site</title> <link rel="stylesheet" href="css/style.css"> </head> <body> <header> <nav> <a href="index.html">Home</a> <a href="about.html">About</a> <a href="contact.html">Contact</a> </nav> </header> <main> <h1>Welcome to My Site</h1> <p>Running on Apache · macOS Golden Gate · MacBook Air M4</p> <section> <h2>About This Site</h2> <p>Pure HTML and CSS — no JavaScript required.</p> </section> </main> <footer> <p>Local development site — mysite.test</p> </footer> </body> </html>

// Create the stylesheet

CSS~/Sites/mysite/css/style.css
/* ── Reset ──────────────────────────────────────── */ * { margin: 0; padding: 0; box-sizing: border-box; } html { font-size: 16px; } /* ── Body ───────────────────────────────────────── */ body { font-family: Georgia, 'Times New Roman', serif; background: #fafaf9; color: #1c1917; line-height: 1.7; max-width: 860px; margin: 0 auto; padding: 0 24px; } /* ── Header / Nav ───────────────────────────────── */ header { border-bottom: 2px solid #1c1917; padding: 20px 0; margin-bottom: 40px; } nav a { color: #1c1917; text-decoration: none; margin-right: 24px; font-weight: 600; font-size: 14px; letter-spacing: 0.04em; text-transform: uppercase; } nav a:hover { color: #ea580c; } /* ── Main content ───────────────────────────────── */ main { padding: 0 0 60px; } h1 { font-size: 2.4rem; font-weight: 700; letter-spacing: -0.03em; margin-bottom: 12px; line-height: 1.1; } h2 { font-size: 1.4rem; margin: 32px 0 12px; } p { color: #78716c; margin-bottom: 16px; } /* ── Footer ─────────────────────────────────────── */ footer { border-top: 1px solid #e7e5e4; padding: 20px 0; font-size: 13px; color: #a8a29e; } /* ── Print styles ───────────────────────────────── */ @media print { nav, footer { display: none; } body { max-width: 100%; font-size: 12pt; } }

// Quick Terminal way to create the files

Terminal — create files fast
# Create homepage
touch ~/Sites/mysite/index.html
touch ~/Sites/mysite/about.html
touch ~/Sites/mysite/contact.html

# Create stylesheet
touch ~/Sites/mysite/css/style.css

# Open folder in Finder to edit in your text editor
open ~/Sites/mysite/

# Or open directly in TextEdit
open -e ~/Sites/mysite/index.html
ℹ️

Use any text editor you like — TextEdit (switch to plain text mode: Format → Make Plain Text), BBEdit, Zed, VS Code, or even nano in Terminal. As long as the file is saved as plain text with a .html or .css extension, Apache will serve it correctly.

TEST

View Your Site

Once your files are in place and Apache is running, visit the site in any browser. You can also access it from your phone or tablet on the same WiFi network.

// Open in browser

Terminal — open site
# Open in your default browser
open http://mysite.test

# Open a specific page
open http://mysite.test/about.html

# Open the CSS file to verify it loads
open http://mysite.test/css/style.css

// Access from your iPhone or iPad on the same WiFi

Terminal — find your local IP
# Get your Mac's local IP address
ipconfig getifaddr en0
# Example output: 192.168.1.25

# Then on your iPhone, type in Safari:
# http://192.168.1.25
# (The /etc/hosts trick only works on your Mac,
#  so use the IP address for other devices)
📱

Using the IP address from another device shows exactly what your site looks like on mobile — no developer tools needed. Great for checking responsive CSS on a real screen.

// Reload without restarting Apache

For a static HTML site, you never need to restart Apache after changing files. Just save the file and refresh the browser — Apache reads the file fresh on every request. Only restart Apache when you change the .conf files.

When you DO need to restart
# Only needed after editing httpd.conf or httpd-vhosts.conf
sudo apachectl configtest   # test first
sudo apachectl graceful      # then restart

# Editing .html or .css files? Just save and refresh browser.
# No restart needed.
TUNE

Apache Settings Useful for HTML/CSS Sites

A few Apache features that improve the experience when serving static HTML and CSS files.

// DirectoryIndex — control the default page

Already set in the VirtualHost above, but here's what it means and how to customize it:

httpd-vhosts.conf — inside your VirtualHost block
# Apache tries these filenames in order when a directory is requested
DirectoryIndex index.html index.htm

// Options Indexes — show a file listing

When Indexes is on and there's no index.html, Apache shows a clickable directory listing. Very useful for browsing a site under construction:

httpd-vhosts.conf — inside Directory block
# Show file listing if no index.html (useful during dev)
Options Indexes FollowSymLinks

# Disable listing when site is "finished" (shows 403 instead)
Options -Indexes FollowSymLinks

// Custom 404 error page

Create a friendly page for broken links. No JavaScript required:

~/Sites/mysite/.htaccess
# Redirect 404 errors to a custom page
ErrorDocument 404 /404.html

# Other useful error pages
ErrorDocument 403 /403.html
ErrorDocument 500 /500.html

// Character encoding — ensure correct display

~/Sites/mysite/.htaccess
# Force UTF-8 for all HTML and CSS files
AddDefaultCharset UTF-8

# Set correct MIME type for CSS (usually automatic, but good to be explicit)
AddType text/css .css
AddType text/html .html .htm

// Prevent directory listing outside your site (security)

~/Sites/mysite/.htaccess
# Hide .htaccess file from browser
<Files .htaccess>
    Require all denied
</Files>

# Prevent access to hidden files (dot files)
<FilesMatch "^\.">
    Require all denied
</FilesMatch>

// Clean URLs — drop the .html extension

Makes /about work instead of /about.html. Requires mod_rewrite (already enabled):

~/Sites/mysite/.htaccess — optional clean URLs
RewriteEngine On

# If file exists as-is, serve it
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]

# Otherwise try adding .html
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^(.+)$ $1.html [L]

# Now http://mysite.test/about works the same as /about.html
PAGES

Linking Between Pages

On a server (vs double-clicking files), links behave differently. Here's the correct way to write links and CSS references so they work perfectly with Apache.

What you wantCorrect linkNotes
Link to About page<a href="about.html">Relative — works from any folder depth
Link from a subdirectory page<a href="/about.html">Absolute from root — safest approach
Load your CSS<link href="/css/style.css">Root-relative — works on every page
Load an image<img src="/images/logo.png">Root-relative prevents path issues
Link to homepage<a href="/"> or <a href="/index.html">Both work correctly
External link<a href="https://...">Always use full URL for external sites
⚠️

Use root-relative paths starting with / for CSS and images — e.g. /css/style.css not css/style.css. This prevents broken paths when pages are in subdirectories.

// About and Contact page starter template

HTML~/Sites/mysite/about.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>About — My Site</title> <link rel="stylesheet" href="/css/style.css"> <!-- note the leading / --> </head> <body> <header> <nav> <a href="/">Home</a> <a href="/about.html">About</a> <a href="/contact.html">Contact</a> </nav> </header> <main> <h1>About</h1> <p>About page content goes here.</p> </main> <footer><p>My Site</p></footer> </body> </html>
DEBUG

Reading Logs & Fixing Problems

// Watch your site's logs in real time

Terminal — open two tabs and run one in each
# Tab 1 — watch requests as they come in
tail -f /private/var/log/apache2/mysite-access.log

# Tab 2 — watch for errors
tail -f /private/var/log/apache2/mysite-error.log

Each line in the access log tells you what file was requested and whether it was found. A 200 means success; a 404 means the file wasn't found (check path and filename).

// Common problems and fixes

ProblemWhat's happeningFix
CSS not loadingPath to stylesheet is wrongChange href="css/style.css" to href="/css/style.css". Check the access log for a 404 on the CSS file.
Image not showingImage path is wrong or file missingUse root-relative path: src="/images/photo.jpg". Verify the file is in ~/Sites/mysite/images/.
403 ForbiddenApache can't read your folderRun ls -la ~/Sites/. Permissions should show drwxr-xr-x. Fix: chmod 755 ~/Sites/mysite.
404 on all pagesDocumentRoot path wrong in vhosts.confConfirm path matches exactly: ls /Users/YOUR_USERNAME/Sites/mysite.
Can't reach mysite.test/etc/hosts entry missingRun grep mysite.test /etc/hosts. If empty, add 127.0.0.1 mysite.test and flush DNS.
Old page showing after editBrowser cacheHard refresh: Cmd+Shift+R in Safari/Chrome. No Apache restart needed.
Blank pageindex.html is empty or malformedCheck the file has content. View source in browser (Cmd+U) to see what Apache actually sent.

// Essential diagnostic commands

Terminal
# Check Apache config is valid
sudo apachectl configtest

# See which vhost serves mysite.test
sudo apachectl -t -D DUMP_VHOSTS

# Verify your /etc/hosts entry
grep mysite.test /etc/hosts

# Check file permissions on your site folder
ls -la ~/Sites/mysite/

# Fix permissions if needed (755 for folders, 644 for files)
chmod -R 755 ~/Sites/mysite/
find ~/Sites/mysite -type f -exec chmod 644 {} \;

# Restart Apache after config changes only
sudo apachectl graceful
LATER

Ready for Perl When You Are

When you're ready to add Perl CGI scripts, very little changes. The VirtualHost config stays the same — you only need to enable two more Apache modules and add a cgi-bin directory. macOS Golden Gate ships with Perl built in at /usr/bin/perl.

🔮

Your current mysite.test VirtualHost is already set up with AllowOverride All, which means you can control Perl CGI behaviour per-directory using .htaccess when the time comes — no changes to the main config files needed.

// What enabling Perl CGI will look like (for reference — not needed now)

httpd.conf — two modules to uncomment later
# Uncomment these in httpd.conf when you're ready for Perl CGI:
LoadModule cgi_module  libexec/apache2/mod_cgi.so
LoadModule cgid_module libexec/apache2/mod_cgid.so
VirtualHost addition — cgi-bin directory
# Add inside your VirtualHost block when ready for Perl:
ScriptAlias /cgi-bin/ "/Users/YOUR_USERNAME/Sites/mysite/cgi-bin/"

<Directory "/Users/YOUR_USERNAME/Sites/mysite/cgi-bin">
    Options       +ExecCGI
    AllowOverride None
    Require       all granted
    AddHandler    cgi-script .pl .cgi
</Directory>

# Perl scripts go in ~/Sites/mysite/cgi-bin/*.pl
# First line of every Perl script must be:
# #!/usr/bin/perl

Your current setup will need zero changes to support Perl later — just uncomment the modules, add the cgi-bin block, and drop your .pl files in. The HTML and CSS files continue to work exactly as they do now.