2025, Dec 04 01:00

Diagnosing Django Internal Server Error on cPanel Apache/mod_wsgi: when AH00124 hides a Python build failure

Learn how a Django app on cPanel with Apache/mod_wsgi threw AH00124 Internal Server Error due to a broken Python build. See the fix and essential config checks.

Deploying Django on cPanel with Apache/mod_wsgi: chasing an Internal Server Error that wasn’t Apache

Rolling out a Django app on a cPanel server with Apache and mod_wsgi can look straightforward until it suddenly isn’t. The symptom in this case was a stubborn Internal Server Error paired with an Apache message about hitting the limit of internal redirects. The environment used two isolated Django instances for production and staging, separate databases and env files, and a manual Apache/mod_wsgi setup because the cPanel account didn’t provide the Setup Python App feature. The application had been working before a server renewal and a reconfiguration of databases, but afterward both domains failed with the same error.

The Apache error log was clear about one thing: Apache thought it was looping. It reported the well-known AH00124 limit message. There were no .htaccess rules in play, and custom app logs were either empty or not capturing anything useful, which hinted that the request might not even reach Django’s logging subsystem.

What the configuration looked like

The deployment relied on cPanel’s Direct Include Style, where httpd.conf pulls in additional per-user config files. The core pieces below show how the WSGI apps were hooked up, static routing, and the WSGI entry point that reads environment variables and sets the correct Django settings module. Names differ from the originals, but behavior is the same.

ServerName example.in
ServerAlias www.example.in
ServerAlias 203.0.113.10

WSGIDaemonProcess svc_main python-home=/home/user/app/venv python-path=/home/user/app user=apacheuser group=appgroup
WSGIProcessGroup grp_main

WSGIScriptAlias / /etc/validator/wsgi/prod.wsgi 

Alias /static/ /home/user/app/staticfiles/

<Directory /home/user/app/staticfiles>
    Require all granted
</Directory>

<Directory /etc/validator/wsgi>
    <Files prod.wsgi>
        Require all granted
    </Files>
</Directory>

ErrorLog /home/user/app/error.log
CustomLog /home/user/app/access.log combined

The staging instance mirrored this approach with its own venv, paths, and WSGI file.

ServerName test.example.in
ServerAlias www.test.example.in

WSGIDaemonProcess svc_stage python-home=/home/user/app_stage/venv python-path=/home/user/app_stage user=apacheuser group=appgroup
WSGIProcessGroup grp_stage

WSGIScriptAlias / /etc/validator/wsgi/stage.wsgi 

Alias /static/ /home/user/app_stage/staticfiles/

<Directory /home/user/app_stage/staticfiles>
    Require all granted
</Directory>

<Directory /etc/validator/wsgi>
    <Files stage.wsgi>
        Require all granted
    </Files>
</Directory>

ErrorLog /home/user/app_stage/error.log
CustomLog /home/user/app_stage/access.log combined

The WSGI entry point loaded the appropriate env file and selected the target settings module.

import os as osmod
import sys as sysmod
from pathlib import Path as Pth
from dotenv import load_dotenv as loadenv

ENV_PATH = Pth('/etc/validator/envs/production.env')
loadenv(dotenv_path=ENV_PATH)

osmod.environ.setdefault('DJANGO_SETTINGS_MODULE', 'coreapp.settings.production')

from django.core.wsgi import get_wsgi_application as get_app
application = get_app()

The production settings leveraged environment variables for database connectivity and tightened DEBUG. Required Django names remain intact; imports and local names are adjusted without changing behavior.

import os as osmod
from .base import *  # noqa

DEBUG = False

ALLOWED_HOSTS = [
    'example',
    'www.example',
    '203.0.113.10',
]

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlbackend',
        'USER': osmod.environ['DBUSER'],
        'PASSWORD': osmod.environ['DBPASSWORD'],
        'NAME': osmod.environ['DBNAME'],
        'HOST': osmod.environ['DBHOST'],
        'PORT': osmod.environ['DBPORT'],
    }
}

What the server actually said

Apache repeatedly reported that it exceeded the recursion limit for internal redirects.

AH00124: Request exceeded the limit of 10 internal redirects due to probable configuration error. Use 'LimitInternalRecursion' to increase the limit if necessary. Use 'LogLevel debug' to get a backtrace.

With no RewriteRule in .htaccess and no custom redirection logic in the snippets above, the message pointed to a loop on the web layer, but the application layer was quiet. That was the paradox: Apache believed it was looping, while Django left no breadcrumbs.

The root cause

Despite the redirect hint in the logs, the underlying issue turned out to be a Python build problem. Some C-extension modules, including those used by pickle, were not built correctly. That broke the runtime under mod_wsgi and resulted in the Internal Server Error. Rebuilding the environment properly resolved the failure.

How it was fixed

The solution was to rebuild the Python environment so the required C-extensions were compiled as expected. No changes to the Apache vhost blocks, the WSGI entry points, or the Django settings were required for the fix.

Why this matters for Django on cPanel with mod_wsgi

Infrastructure messages can be misleading when the application runtime collapses before it can log. You can see an AH00124 redirect warning and still face a lower-level failure in the Python interpreter or its extensions. When your WSGI process dies early, Apache may surface a generic Internal Server Error, and the surrounding context can suggest an HTTP misconfiguration even when the true fault lies in the Python build.

This is especially relevant when running multiple Django instances with separate virtual environments, distinct WSGI files, and per-domain Apache includes. Each environment must be healthy on its own. If shared assumptions about the Python toolchain change after a server renewal or after reconfiguring dependencies, you can end up debugging the wrong layer.

Takeaways and closing notes

If you encounter an Internal Server Error on a mod_wsgi deployment and Apache claims it is hitting internal redirect limits, do not stop at the web server rules. Validate that the Python runtime and any C-extension modules your stack relies on are correctly built. In setups with cPanel, where you manually wire WSGI daemons and per-domain configs, the web layer might be fine while the interpreter underneath is not. Once the Python environment was rebuilt and the extensions compiled properly, the deployment worked again.