← Back to Apache Course | Chapter 7: Advanced | Lesson 4 of 4

Apache with Django/PHP

This final lesson ties the course together with two complete, realistic setups: serving a PHP application directly, and reverse-proxying to a Django application running behind Gunicorn.

PHP via PHP-FPM (mod_proxy_fcgi)

The modern way to run PHP under Apache runs PHP-FPM as its own separate process pool and connects Apache to it via mod_proxy_fcgi, rather than embedding PHP directly into Apache with the older mod_php. Enable it with sudo a2enmod proxy_fcgi setenvif plus PHP's own FPM Apache config (often already provided by the distro's libapache2-mod-fcgid/php-fpm packages), then point requests for .php files at the FPM socket.

A Complete PHP VirtualHost

The DocumentRoot points at the PHP application's public folder as usual. A <FilesMatch \.php$> block hands off any request for a .php file to the FPM socket via SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost" (adjusting the socket path/PHP version to match what's installed) -- Apache still serves the application's static assets (CSS, JS, images) directly itself, only handing off .php requests to FPM.

Django via a Reverse Proxy to Gunicorn

Django doesn't run inside Apache the way PHP can -- it runs as its own Python process, typically managed by Gunicorn and started/kept alive by systemd. Apache's job is purely what the Reverse Proxy lesson covered: ProxyPass/ProxyPassReverse everything through to Gunicorn's address (commonly a Unix socket or 127.0.0.1:8000), while serving Django's collected static and media files directly itself via an Alias, since Django's own development server isn't meant to serve them in production.

Serving Static Files Directly, Either Way

For both PHP and Django, the same principle applies: let Apache serve static files (CSS, JS, images) directly from disk with an Alias and a <Directory> block, and only hand dynamic requests off to PHP-FPM or Gunicorn. Apache is significantly more efficient at serving a static file than routing it through the application layer just to read it off disk unchanged.

Note: Whichever backend you're running, keep the pattern the same: Apache serves static files directly, and only proxies (or hands off via FPM) the requests that actually need application code to run.

Example: PHP-FPM and Django/Gunicorn VirtualHosts side by side

apacheconf
# PHP application via PHP-FPM
<VirtualHost *:80>
    ServerName php-app.example.com
    DocumentRoot /var/www/php-app/public

    <FilesMatch \.php$>
        SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost"
    </FilesMatch>
</VirtualHost>

# Django application via Gunicorn
<VirtualHost *:80>
    ServerName django-app.example.com

    Alias /static/ /var/www/django-app/static/
    <Directory /var/www/django-app/static>
        Require all granted
    </Directory>

    ProxyPreserveHost On
    ProxyPass /static/ !
    ProxyPass "/" "http://127.0.0.1:8000/"
    ProxyPassReverse "/" "http://127.0.0.1:8000/"
</VirtualHost>
{# Flagged by hand after confirming a runner can't handle this example (a shell command / go.mod file stored as a TopicExample, a language feature the configured runner version doesn't support, or output that blows a runner's sandbox limit) -- see TopicExample.norun. Never render the run button for these, regardless of language, since it would just fail at execute_code (or worse, hang the Judge0 queue on a submission that can never finish cleanly). #}

⚠️ This example can't run in the browser editor. Try it in your own local environment instead.

{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Proxying every request -- including static files -- through to Gunicorn/PHP-FPM, when Apache serving them directly is both simpler and considerably faster.
  2. Mismatching the PHP-FPM socket path or PHP version in the SetHandler directive against what's actually installed, producing a 502/503 error for every .php request.
  3. Running Django's manage.py runserver behind Apache in production instead of a real WSGI server like Gunicorn -- the development server isn't designed or hardened for that.
Chapter Summary
  • A reverse proxy lets Apache be the single public-facing entry point for one or more backend application servers, handling TLS/static files/logging while forwarding dynamic requests on -- and load balancing extends the same idea across multiple backend instances.
  • URL rewriting in practice mostly serves three needs -- clean URLs into a framework's front controller, redirecting an old site's URLs to their new locations, and enforcing one canonical domain/protocol -- and real PHP and Django deployments combine everything from this course: VirtualHosts, TLS, proxying, and serving static assets directly.
🔒

Chapter Quiz — Complete all 4 topics to unlock

0/4 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.