Capstone: Designing and Hardening a Production Apache Deployment

Apache In Depth

Chapter 10 · Capstone: Designing and Hardening a Production Apache Deployment

Web Servers Fundamentals' own capstone compared three servers across several scenarios. This one is different, by design — the same shape Nginx In Depth's own capstone used: a single, cohesive worked example, layering in each of this course's own eight prior chapters, one at a time, until one complete, production-shaped configuration exists.

The Scenario

One Apache instance hosts two real things: a customer-facing PHP web application at www.example.com, and a small internal API — a separate Node.js service — proxied at api.example.com. Both need HTTPS, sensible performance, and hardening that reflects everything this course has covered.

Step 1 — MPM Choice (Chapter 2)

<IfModule mpm_event_module> ServerLimit 16 ThreadsPerChild 25 MaxRequestWorkers 400 </IfModule>

The PHP application runs on PHP-FPM, not traditional mod_php — per Chapter 2, that's exactly what frees Apache to run Event here instead of being forced onto Prefork, gaining Event's lower per-connection overhead for both the PHP site and the proxied API sharing this same instance.

Step 2 — Locking Down .htaccess (Chapter 3)

<Directory "/var/www"> AllowOverride None </Directory>

Every piece of configuration for both sites lives in version-controlled main config files, not scattered .htaccess overrides — so AllowOverride None applies globally, per Chapter 3's own hardening payoff: this removes the per-request filesystem scan entirely and closes off an entire class of misconfiguration risk from a stray or compromised .htaccess file, with nothing lost since neither site actually needs one.

Step 3 — Core Modules for the PHP App (Chapter 4)

RewriteEngine On RewriteRule ^article/([0-9]+)$ article.php?id=$1 [L] Header set X-Frame-Options "SAMEORIGIN" <IfModule mod_deflate.c> AddOutputFilterByType DEFLATE text/html text/css application/javascript </IfModule>

Clean article URLs via mod_rewrite, a baseline security header via mod_headers, and compression for the PHP app's own text-based output via mod_deflate — the three Chapter 4 modules, all falling under the same FileInfo group Step 2 didn't need to grant anywhere else.

Step 4 — Two Domains, One Apache Instance (Chapter 5)

<VirtualHost *:443> ServerName www.example.com DocumentRoot /var/www/site </VirtualHost> <VirtualHost *:443> ServerName api.example.com </VirtualHost>

Both domains share the same IP and port, distinguished purely by ServerName — modern name-based hosting over HTTPS, made possible entirely by SNI (Chapter 5), with no separate IP needed per domain the way it would have before SNI existed.

Step 5 — TLS Hardening (Chapter 6)

SSLEngine on SSLCertificateFile /etc/ssl/certs/example.com-fullchain.crt SSLCertificateKeyFile /etc/ssl/private/example.com.key SSLProtocol -all +TLSv1.2 +TLSv1.3 SSLCipherSuite HIGH:!aNULL:!MD5:!3DES SSLUseStapling on

Applied inside each <VirtualHost *:443> block: the modern combined-chain certificate file, a fail-safe SSLProtocol line, a curated cipher list, and OCSP stapling so clients confirm certificate validity without a live round-trip to the CA — all four directly from Chapter 6.

Step 6 — Proxying the Internal API (Chapter 7)

<Proxy "balancer://apipool"> BalancerMember "http://10.0.0.11:3000" BalancerMember "http://10.0.0.12:3000" ProxySet lbmethod=bybusyness </Proxy> ProxyPreserveHost On ProxyPass "/" "balancer://apipool/" ProxyPassReverse "/" "balancer://apipool/"

Inside api.example.com's own <VirtualHost>: a two-member balanced pool using bybusyness, since this API's own endpoints vary widely in response time, and ProxyPreserveHost On so the Node.js service sees the real requested hostname rather than the internal pool address — exactly the gotcha Chapter 7's own warning box covered.

Step 7 — Access Control & mod_security (Chapter 8)

<Location "/internal-metrics"> Require ip 10.0.0.0/8 </Location> SecRuleEngine DetectionOnly

The API's own internal metrics endpoint is IP-restricted to the private network via Require ip — controlling who can reach it. Site-wide, mod_security with the OWASP Core Rule Set starts in DetectionOnly, per Chapter 8's own recommended rollout, rather than risking false-positive outages by enabling On from day one.

Step 8 — Performance, Logging & Pre-Flight Checks (Chapter 9)

KeepAliveTimeout 15 CacheRoot /var/cache/apache2/mod_cache_disk CacheEnable disk /static/ LogFormat "%h %l %u %t \"%r\" %>s %b" combined CustomLog /var/log/apache2/access.log combined # Before every reload: # apachectl configtest

KeepAliveTimeout 15 is comfortably affordable specifically because Step 1 chose Event, not Prefork — the same setting would have cost real MaxRequestWorkers capacity under Prefork, per Chapter 2's own math. Caching is scoped to /static/ only, deliberately excluding the PHP app's own logged-in pages, per Chapter 9's own warning about personalized content and cache hit rate. apachectl configtest is the last, non-negotiable step before any of this ever gets reloaded live.

Capstone StepChapter It Draws From
Step 1 — MPM choiceChapter 2
Step 2 — AllowOverride NoneChapter 3
Step 3 — mod_rewrite/headers/deflateChapter 4
Step 4 — Name-based hosting via SNIChapter 5
Step 5 — TLS hardeningChapter 6
Step 6 — Proxying & load balancingChapter 7
Step 7 — Access control & mod_securityChapter 8
Step 8 — Performance, caching & loggingChapter 9
Two capstones, one full journey
Web Servers Fundamentals' own capstone answered "which server fits this job." This one answers "given Apache was already chosen, how do you actually configure it well" — the same relationship Nginx In Depth's own capstone has to that same earlier course. Together, all three capstones across the Web Servers subject cover the full journey from choosing a web server to running a genuinely production-shaped configuration of whichever one was chosen.
This config is a solid foundation, not a complete go-live checklist
Every directive above reflects real material from this course, but a genuine production rollout still needs steps beyond any single config file: validating the config (apachectl configtest) before every reload, a gradual rollout rather than switching all traffic at once, and real alerting configured on top of whatever monitoring is in place — well beyond mod_status's or balancer-manager's own live snapshots. Treat this chapter's config as a strong, correct starting point, not a substitute for an actual deployment process.

Hands-On Exercises

Exercise 1

This capstone applies AllowOverride None globally in Step 2, even though earlier chapters spent real time explaining how .htaccess and AllowOverride's named groups work. Explain why this is not a contradiction, referencing this chapter's own reasoning.

📄 View solution
Exercise 2

A new /account/ endpoint is added to the PHP app, showing each logged-in user their own personalized order history. Should it be added to the CacheEnable disk zone the same way /static/ was in Step 8? Explain your answer using this course's own material.

📄 View solution
Exercise 3

Write a short chapter-attribution summary (2-3 sentences) explaining how this capstone's own shape differs from Web Servers Fundamentals' own capstone, and why that difference makes sense given what each course actually covers.

📄 View solution
Course Complete

Apache In Depth — 10 of 10 chapters complete. The Web Servers subject now has its comparative foundation, its Nginx deep dive, and its Apache deep dive.

Chapter 10 Quick Reference

  • This capstone builds one cohesive production config for two real domains, layering in Chapters 2 through 9 step by step — the same deliberate shape as Nginx In Depth's own capstone, and a different one from Web Servers Fundamentals' own multi-scenario decision framework
  • PHP-FPM (not mod_php) is what makes choosing Event over Prefork possible in Step 1 — a payoff planted all the way back in Chapter 2
  • Only non-personalized content belongs in a mod_cache_disk zone; personalized pages need Cache-Control: private respected instead, not a cleverly-keyed cache (Chapter 9)
  • A working config is a foundation, not a complete production rollout — apachectl configtest, gradual deployment, and real alerting still matter beyond this chapter's own scope