Apache Cheat Sheet: httpd Config, VirtualHost, mod_rewrite, and apachectl
A practical Apache cheat sheet covering VirtualHost, DocumentRoot, mod_rewrite, .htaccess, enabling modules, apachectl, and reverse proxying.
Apache Cheat Sheet: httpd Config, VirtualHost, mod_rewrite, and apachectl
Apache HTTP Server still runs a large slice of the web, and most of the work is the same handful of directives applied over and over: define a VirtualHost, point a DocumentRoot at some files, grant access in a Directory block, rewrite a few URLs, enable a module or two, and reload without dropping connections. This page is a quick reference for that day-to-day config work. If you want a searchable, filterable version you can paste a snippet into, the Apache Cheatsheet tool keeps the same directives one keystroke away.
The directives you actually type every day
Apache config is built from directives and containers. The containers wrap rules around a scope; the directives set behavior inside. The ones that show up in almost every config:
ServerName— the canonical hostname this server answers for.DocumentRoot— the directory on disk that maps to the URL root.<VirtualHost>— a named host bound to an IP/port, usually*:80or*:443.<Directory>— permissions and options for a filesystem path.Require— who is allowed in (all granted,all denied,ip 10.0.0.0/8).Options— feature flags likeFollowSymLinks,-Indexes.AllowOverride— which directives.htaccessmay override (keep it narrow).RewriteEngine/RewriteRule/RewriteCond— URL rewriting via mod_rewrite.ProxyPass/ProxyPassReverse— reverse-proxy mapping to an upstream.
On Debian and Ubuntu the binary is apache2, configs live under /etc/apache2/, and sites are toggled with a2ensite / a2dissite. On RHEL, Rocky, and Alma the binary is httpd, configs live under /etc/httpd/conf.d/, and you edit drop-in .conf files directly. The directives are identical; only the paths and the wrapper scripts differ.
A real VirtualHost block
Here is a complete name-based VirtualHost for a static site on port 80. Nothing exotic, just the shape you will copy a hundred times:
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/public
<Directory "/var/www/example.com/public">
Options -Indexes +FollowSymLinks
AllowOverride None
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/example.com-error.log
CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined
</VirtualHost>
A few things are deliberate here. Options -Indexes turns off directory listings so a missing index file returns 404 instead of exposing the folder contents. AllowOverride None means Apache never scans this tree for .htaccess files, which is both faster and easier to reason about. Require all granted is the line people forget — without it you get a 403 even when the files are perfectly readable.
mod_rewrite without the guesswork
mod_rewrite is where most "why isn't this redirecting" tickets come from. The engine has to be switched on, rules run top to bottom, and conditions apply only to the rule directly below them. A canonical HTTPS redirect plus a non-www-to-www rule looks like this:
RewriteEngine On
# Force HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
# Redirect bare domain to www
RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
RewriteRule ^ https://www.example.com%{REQUEST_URI} [R=301,L]
The flags carry the meaning. R=301 issues a permanent redirect, L stops processing further rules for that pass, and NC makes the host match case-insensitive. If you are sending caching or security headers alongside these redirects, build them in Cache-Control Builder and paste the result into a Header set directive rather than hand-typing the directive syntax.
Enabling modules and reloading safely
Rewrite, proxy, SSL, and headers all live in modules that have to be loaded before their directives work. On Debian/Ubuntu:
a2enmod rewrite proxy proxy_http ssl headers
systemctl reload apache2
On RHEL the modules are usually loaded by LoadModule lines in /etc/httpd/conf.modules.d/, so you uncomment instead of running a wrapper. Either way, the rule I never skip is to test before reloading:
apachectl configtest && apachectl graceful
configtest parses the whole config and reports Syntax OK or the exact file and line of the problem. graceful finishes in-flight requests on the old workers and starts new ones on the fresh config, so live visitors never see a dropped connection. The && matters: if configtest fails, the reload never runs, and a typo can't take the site down.
A worked scenario: standing up a new vhost
Last month I moved a small marketing site onto an existing box that was already serving two other domains. The whole thing took about ten minutes, and the steps are worth writing down because they generalize.
First I created the document root and dropped the files in: mkdir -p /var/www/promo.example.com/public. Then I wrote a vhost file (/etc/apache2/sites-available/promo.example.com.conf) using the VirtualHost block above, swapping in the new ServerName and DocumentRoot. I enabled it with a2ensite promo.example.com, confirmed permissions with chown -R www-data:www-data /var/www/promo.example.com, and ran apachectl configtest. It flagged a missing closing </Directory> — exactly the kind of thing you want caught before a reload, not after. I fixed the tag, ran configtest again to a clean Syntax OK, then apachectl graceful. The site answered on the first request. The only follow-up was confirming the access log was actually filling, which is the fastest signal that name-based routing is matching the right vhost.
If you are confirming a vhost from the command line instead of a browser, cURL Cheatsheet has the --resolve and -H "Host:" patterns that let you hit a specific vhost before DNS has even propagated.
Reverse proxying to an app server
When the document root is a backend app instead of files on disk, Apache becomes a reverse proxy. The two directives you always write together:
<VirtualHost *:80>
ServerName app.example.com
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:3000/
ProxyPassReverse / http://127.0.0.1:3000/
</VirtualHost>
ProxyPass forwards requests to the upstream; ProxyPassReverse rewrites the Location, Content-Location, and URI headers on responses so redirects from the app don't leak 127.0.0.1:3000 back to the browser. Writing one without the other is one of the most common Apache mistakes, and it only shows up when the app issues a redirect — which is exactly when it's hardest to debug. ProxyPreserveHost On passes the original Host header through so the app knows which site it's serving.
A quick reference to keep nearby
- Test config:
apachectl configtest(orhttpd -t) - Graceful reload:
apachectl graceful/systemctl reload apache2 - Enable a site (Debian):
a2ensite NAMEthen reload - Enable a module (Debian):
a2enmod rewritethen reload - Show loaded modules:
apachectl -M - Show vhost mapping:
apachectl -S - Tail the error log:
tail -f /var/log/apache2/error.log
For comparing how the same job is done on the other big web server, the Nginx Cheatsheet lines up location blocks against Apache's <Directory> and proxy_pass against ProxyPass. Keep AllowOverride narrow, treat ProxyPass and ProxyPassReverse as a pair, always configtest before you graceful, and most of Apache stops being a guessing game.
Made by Toolora · Updated 2026-06-13