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.
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.
Works for single pages. Relative paths can break. No network access from other devices. URLs look like file:///Users/you/...
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.
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:
# 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
Three files to edit, in order. All require sudo for the Apache config files. Replace YOUR_USERNAME with your actual macOS username everywhere.
Open the main Apache config and uncomment the vhosts include line.
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:
Include /private/etc/apache2/extra/httpd-vhosts.confAlso search for mod_rewrite and uncomment (useful for clean URLs later):
LoadModule rewrite_module libexec/apache2/mod_rewrite.so
Ctrl+O saves in nano, Ctrl+X exits. Search with Ctrl+W.
Open the vhosts config file and add your site block. The default catch-all must always come first.
sudo nano /etc/apache2/extra/httpd-vhosts.conf
Replace the entire file contents with this — change YOUR_USERNAME on each line:
# ── 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>
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
# 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.
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 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.
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 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
# 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.
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.
# 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.
A few Apache features that improve the experience when serving static HTML and CSS files.
Already set in the VirtualHost above, but here's what it means and how to customize it:
# Apache tries these filenames in order when a directory is requested DirectoryIndex index.html index.htm
When Indexes is on and there's no index.html, Apache shows a clickable directory listing. Very useful for browsing a site under construction:
# 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
Create a friendly page for broken links. No JavaScript required:
# Redirect 404 errors to a custom page ErrorDocument 404 /404.html # Other useful error pages ErrorDocument 403 /403.html ErrorDocument 500 /500.html
# 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
# Hide .htaccess file from browser <Files .htaccess> Require all denied </Files> # Prevent access to hidden files (dot files) <FilesMatch "^\."> Require all denied </FilesMatch>
Makes /about work instead of /about.html. Requires mod_rewrite (already enabled):
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
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 want | Correct link | Notes |
|---|---|---|
| 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.
# 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).
| Problem | What's happening | Fix |
|---|---|---|
| CSS not loading | Path to stylesheet is wrong | Change href="css/style.css" to href="/css/style.css". Check the access log for a 404 on the CSS file. |
| Image not showing | Image path is wrong or file missing | Use root-relative path: src="/images/photo.jpg". Verify the file is in ~/Sites/mysite/images/. |
| 403 Forbidden | Apache can't read your folder | Run ls -la ~/Sites/. Permissions should show drwxr-xr-x. Fix: chmod 755 ~/Sites/mysite. |
| 404 on all pages | DocumentRoot path wrong in vhosts.conf | Confirm path matches exactly: ls /Users/YOUR_USERNAME/Sites/mysite. |
| Can't reach mysite.test | /etc/hosts entry missing | Run grep mysite.test /etc/hosts. If empty, add 127.0.0.1 mysite.test and flush DNS. |
| Old page showing after edit | Browser cache | Hard refresh: Cmd+Shift+R in Safari/Chrome. No Apache restart needed. |
| Blank page | index.html is empty or malformed | Check the file has content. View source in browser (Cmd+U) to see what Apache actually sent. |
# 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
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.
# 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
# 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.