How to install and configure Python with Apache

How to install and configure Python with Apache

Python and Apache don’t naturally speak the same language out of the box, and I remember being confused early on about why my Flask app wouldn’t just “run” when I dropped it into the DocumentRoot. The missing piece is WSGI — the interface that lets Apache hand off requests to Python code. In this guide, I’ll walk through installing and configuring Python with Apache using mod_wsgi, the most common and reliable approach.

Understanding How Python Works with Apache

Python web frameworks like Flask and Django follow the WSGI (Web Server Gateway Interface) specification, a standard that defines how a web server communicates with a Python application. Apache doesn’t understand WSGI natively, so it needs a bridge: mod_wsgi.

mod_wsgi embeds a Python interpreter inside Apache (or runs it in separate daemon processes) and handles translating HTTP requests into calls your Python application understands, then converts the application’s response back into an HTTP response.

There are two operating modes:

  • Embedded mode – Python runs inside Apache’s own worker processes. Simpler but less isolated.
  • Daemon mode – Python runs in separate, dedicated processes managed by mod_wsgi. Recommended for production because it isolates your app from Apache’s process lifecycle and allows independent restarts.

Prerequisites

  • A Linux server with Apache 2.4+ installed
  • Root or sudo access
  • Python 3.8+ installed (python3 --version)
  • pip and venv available
  • A Python web application (Flask or Django example used below)

Step 1: Install Python and Development Tools

Debian/Ubuntu:

sudo apt update
sudo apt install python3 python3-pip python3-venv python3-dev apache2 apache2-dev libapache2-mod-wsgi-py3

CentOS/RHEL:

sudo dnf install python3 python3-pip python3-devel httpd httpd-devel
sudo dnf install python3-mod_wsgi

Step 2: Enable mod_wsgi

Debian/Ubuntu:

sudo a2enmod wsgi
sudo systemctl restart apache2

Verify it loaded:

apache2ctl -M | grep wsgi

CentOS/RHEL:

Ensure this line exists in your Apache configuration:

LoadModule wsgi_module modules/mod_wsgi.so

Then restart:

sudo systemctl restart httpd

Step 3: Set Up a Python Virtual Environment

Isolating dependencies in a virtual environment is essential for production stability:

sudo mkdir -p /var/www/myflaskapp
cd /var/www/myflaskapp
python3 -m venv venv
source venv/bin/activate
pip install flask

Step 4: Create a Simple Flask Application

nano /var/www/myflaskapp/app.py
from flask import Flask

app = Flask(__name__)

@app.route("/")
def home():
    return "Hello from Flask running on Apache with mod_wsgi!"

if __name__ == "__main__":
    app.run()

Step 5: Create the WSGI Entry Point

nano /var/www/myflaskapp/myflaskapp.wsgi
#!/usr/bin/python3
import sys
import logging

logging.basicConfig(stream=sys.stderr)

sys.path.insert(0, "/var/www/myflaskapp/")

from app import app as application

The variable must be named application — this is the convention mod_wsgi looks for.

Step 6: Configure the Apache Virtual Host


    ServerName myflaskapp.com

    WSGIDaemonProcess myflaskapp python-home=/var/www/myflaskapp/venv python-path=/var/www/myflaskapp
    WSGIProcessGroup myflaskapp
    WSGIScriptAlias / /var/www/myflaskapp/myflaskapp.wsgi

    
        Require all granted
    

    ErrorLog ${APACHE_LOG_DIR}/myflaskapp_error.log
    CustomLog ${APACHE_LOG_DIR}/myflaskapp_access.log combined

WSGIDaemonProcess with python-home pointing to the virtual environment ensures Apache uses your isolated Python packages rather than the system-wide installation.

Enable the site and restart:

sudo a2ensite myflaskapp.conf
sudo apachectl configtest
sudo systemctl restart apache2

Visit http://myflaskapp.com — you should see the Flask response.

Configuring Django with Apache

Django follows a similar pattern but with a slightly different WSGI entry point structure, since Django generates wsgi.py automatically.

Step 1: Set Up the Django Project

cd /var/www
python3 -m venv django_env
source django_env/bin/activate
pip install django
django-admin startproject myproject

Step 2: Configure the Apache Virtual Host


    ServerName mydjangoapp.com

    WSGIDaemonProcess myproject python-home=/var/www/django_env python-path=/var/www/myproject
    WSGIProcessGroup myproject
    WSGIScriptAlias / /var/www/myproject/myproject/wsgi.py

    
        
            Require all granted
        
    

    Alias /static/ /var/www/myproject/static/
    
        Require all granted
    

    ErrorLog ${APACHE_LOG_DIR}/mydjangoapp_error.log
    CustomLog ${APACHE_LOG_DIR}/mydjangoapp_access.log combined

Don’t forget to run collectstatic before going live:

python manage.py collectstatic

Real-World Use Cases

  • Data-driven web dashboards – Python’s data science ecosystem (pandas, matplotlib) pairs naturally with Flask/Django apps serving visualizations.
  • Internal admin tools – Django’s built-in admin panel is a common choice for internal business applications deployed behind Apache.
  • REST APIs – Flask or Django REST Framework backends serving mobile or JavaScript frontend applications.
  • Machine learning model serving – Lightweight Flask apps wrapping ML models for inference, deployed behind Apache for SSL and load balancing.

Common Mistakes to Avoid

  1. Forgetting python-home in WSGIDaemonProcess – Without it, Apache uses the system Python, missing your virtual environment’s packages, leading to ModuleNotFoundError.
  2. Naming the WSGI callable incorrectly – It must be named application, not app, in the .wsgi file (even though your Flask variable can be named app inside app.py, the imported alias in the wsgi file must be application).
  3. Running the dev server in productionapp.run() or Django’s runserver are not production-grade; always use mod_wsgi (or Gunicorn behind a proxy) instead.
  4. Mixing Python 2 and Python 3 mod_wsgi packages – Ensure you install libapache2-mod-wsgi-py3 specifically, not the Python 2 version.
  5. Incorrect file permissions – The Apache user needs read access to your application directory and virtual environment.

Security Best Practices

  • Set DEBUG = False in Django’s settings for production; leaving debug mode on exposes sensitive stack traces and environment details.
  • Store secrets (SECRET_KEY, database credentials) in environment variables, not hardcoded in source files.
  • Restrict ALLOWED_HOSTS in Django to your actual domain(s).
  • Run WSGIDaemonProcess under a dedicated, non-privileged user rather than the default Apache user where isolation matters.
  • Keep Python, Flask/Django, and all dependencies updated; use pip list --outdated and tools like pip-audit regularly.

Performance Optimization Tips

  • Use daemon mode (not embedded mode) for better process isolation and easier scaling.
  • Tune processes and threads in WSGIDaemonProcess based on your workload:
WSGIDaemonProcess myflaskapp python-home=/var/www/myflaskapp/venv processes=4 threads=2
  • Serve static files directly through Apache (Alias directives) rather than through the Python application.
  • Combine with the compression and caching techniques from our other Apache guides.
  • Consider an alternative like Gunicorn or uWSGI behind Apache’s mod_proxy for very high-concurrency workloads, as some teams find this scales more predictably than mod_wsgi.

Troubleshooting

500 Internal Server Error with no clear message:

  • Check error.log: sudo tail -f /var/log/apache2/myflaskapp_error.log. mod_wsgi logs Python tracebacks here.

ModuleNotFoundError for installed packages:

  • Confirm python-home points to the correct virtual environment and that the package was installed inside that venv, not globally.

Static files return 404 in Django:

  • Run collectstatic and verify the Alias /static/ path matches STATIC_ROOT in settings.py.

Changes to code not reflecting:

  • mod_wsgi daemon processes cache the application; restart Apache or touch the .wsgi file to force a reload:
touch /var/www/myflaskapp/myflaskapp.wsgi

Frequently Asked Questions

Do I need mod_wsgi, or can I use Gunicorn instead? Both work. mod_wsgi embeds Python directly into Apache’s process management. Gunicorn runs as a separate process, with Apache acting as a reverse proxy (similar to the Rails/Puma pattern). Gunicorn behind a proxy is increasingly common for containerized deployments; mod_wsgi remains popular for traditional VPS setups already standardized on Apache.

Can I run multiple Python apps on the same Apache server? Yes, using separate WSGIDaemonProcess groups and virtual hosts, each with its own virtual environment and process isolation.

Why does my app work with python app.py but not through Apache? The built-in Flask/Django development server behaves differently from mod_wsgi’s process model — typically the issue is either the python-home path, file permissions, or a missing environment variable that was set locally but not in Apache’s environment.

Is mod_wsgi actively maintained? Yes, mod_wsgi continues to be maintained and is a stable, production-proven choice for serving Python WSGI applications through Apache.

Summary and Key Takeaways

  • Python applications communicate with Apache through the WSGI interface, bridged by mod_wsgi.
  • Always use a virtual environment and point WSGIDaemonProcess to it via python-home.
  • Daemon mode is preferred over embedded mode for production isolation and stability.
  • Django requires additional static file configuration (collectstatic + Alias) that Flask doesn’t need out of the box.
  • Never run Flask’s or Django’s built-in development server in production — mod_wsgi (or Gunicorn behind a proxy) is the correct approach.
  • Check the Apache error log first whenever something goes wrong; Python tracebacks are logged there.

References

Total
3
Shares

Leave a Reply

Previous Post
How to install and configure PHP on Apache

How to Install and Configure PHP on Apache

Next Post
How to use Apache with Ruby on Rails

How to Use Apache with Ruby on Rails

Related Posts